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 1620da3f528..c372cdfa6b2 100644 --- a/compiler/frontend/src/org/jetbrains/jet/lang/diagnostics/Errors.java +++ b/compiler/frontend/src/org/jetbrains/jet/lang/diagnostics/Errors.java @@ -594,6 +594,7 @@ public interface Errors { //Inline and inlinable parameters DiagnosticFactory2 INVISIBLE_MEMBER_FROM_INLINE = DiagnosticFactory2.create(ERROR, CALL_ELEMENT); + DiagnosticFactory3 NON_LOCAL_RETURN_NOT_ALLOWED = DiagnosticFactory3.create(ERROR, CALL_ELEMENT); DiagnosticFactory2 NOT_YET_SUPPORTED_IN_INLINE = DiagnosticFactory2.create(ERROR); DiagnosticFactory1 NOTHING_TO_INLINE = DiagnosticFactory1.create(WARNING, NAMED_ELEMENT); DiagnosticFactory2 USAGE_IS_NOT_INLINABLE = DiagnosticFactory2.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 bb804680559..e04239b7eba 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 @@ -515,6 +515,8 @@ public class DefaultErrorMessages { MAP.put(USAGE_IS_NOT_INLINABLE, "Illegal usage of inline-parameter ''{0}'' in ''{1}''. Annotate the parameter with [noinline]", ELEMENT_TEXT, SHORT_NAMES_IN_TYPES); MAP.put(NULLABLE_INLINE_PARAMETER, "Inline-parameter ''{0}'' of ''{1}'' must not be nullable. Annotate the parameter with [noinline] or make not nullable", ELEMENT_TEXT, SHORT_NAMES_IN_TYPES); MAP.put(RECURSION_IN_INLINE, "Inline-function ''{1}'' can't be recursive", ELEMENT_TEXT, SHORT_NAMES_IN_TYPES); + //Inline non Locals + MAP.put(NON_LOCAL_RETURN_NOT_ALLOWED, "Can''t inline ''{0}'' here: it may contain non-local returns. Annotate parameter declaration ''{0}'' with ''inlineOptions(ONLY_LOCAL_RETURNS)''", ELEMENT_TEXT, SHORT_NAMES_IN_TYPES, SHORT_NAMES_IN_TYPES); MAP.setImmutable(); 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 540517c5679..c607278f230 100644 --- a/compiler/frontend/src/org/jetbrains/jet/lang/psi/JetPsiUtil.java +++ b/compiler/frontend/src/org/jetbrains/jet/lang/psi/JetPsiUtil.java @@ -884,4 +884,37 @@ public class JetPsiUtil { return element instanceof JetSimpleNameExpression && ((JetSimpleNameExpression) element).getReferencedNameElementType() == JetTokens.LABEL_IDENTIFIER; } + + @Nullable + public static JetExpression getParentCallIfPresent(@NotNull JetExpression expression) { + PsiElement parent = expression.getParent(); + while (parent != null) { + if (parent instanceof JetBinaryExpression || + parent instanceof JetUnaryExpression || + parent instanceof JetLabeledExpression || + parent instanceof JetDotQualifiedExpression || + parent instanceof JetCallExpression || + parent instanceof JetArrayAccessExpression || + parent instanceof JetMultiDeclaration) { + + if (parent instanceof JetLabeledExpression) { + parent = parent.getParent(); + continue; + } + + //check that it's in inlineable call would be in resolve call of parent + return (JetExpression) parent; + } + else if (parent instanceof JetParenthesizedExpression || parent instanceof JetBinaryExpressionWithTypeRHS) { + parent = parent.getParent(); + } + else if (parent instanceof JetValueArgument || parent instanceof JetValueArgumentList) { + parent = parent.getParent(); + } + else { + return null; + } + } + return null; + } } diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/resolve/BindingContextUtils.java b/compiler/frontend/src/org/jetbrains/jet/lang/resolve/BindingContextUtils.java index b531c2c7c4a..9ecd7c4685f 100644 --- a/compiler/frontend/src/org/jetbrains/jet/lang/resolve/BindingContextUtils.java +++ b/compiler/frontend/src/org/jetbrains/jet/lang/resolve/BindingContextUtils.java @@ -328,17 +328,18 @@ public class BindingContextUtils { } @NotNull - public static Pair getLambdaContainingFunction( - @NotNull FunctionDescriptor lambdaDescriptor, - @NotNull BindingContext context + public static Pair getContainingFunctionSkipFunctionLiterals( + @NotNull BindingContext context, + @Nullable DeclarationDescriptor startDescriptor, + boolean strict ) { - FunctionDescriptor containingFunctionDescriptor = lambdaDescriptor; - PsiElement containingFunction; - do { + FunctionDescriptor containingFunctionDescriptor = DescriptorUtils.getParentOfType(startDescriptor, FunctionDescriptor.class, strict); + PsiElement containingFunction = containingFunctionDescriptor != null ? callableDescriptorToDeclaration(context, containingFunctionDescriptor) : null; + while (containingFunction instanceof JetFunctionLiteral) { containingFunctionDescriptor = DescriptorUtils.getParentOfType(containingFunctionDescriptor, FunctionDescriptor.class); containingFunction = containingFunctionDescriptor != null ? callableDescriptorToDeclaration(context, containingFunctionDescriptor) : null; - } while (containingFunction instanceof JetFunctionLiteral); + } return new Pair(containingFunctionDescriptor, containingFunction); } diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/resolve/InlineDescriptorUtils.java b/compiler/frontend/src/org/jetbrains/jet/lang/resolve/InlineDescriptorUtils.java new file mode 100644 index 00000000000..6768b13e308 --- /dev/null +++ b/compiler/frontend/src/org/jetbrains/jet/lang/resolve/InlineDescriptorUtils.java @@ -0,0 +1,81 @@ +/* + * Copyright 2010-2014 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.intellij.psi.PsiElement; +import com.intellij.psi.util.PsiTreeUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.jet.lang.descriptors.*; +import org.jetbrains.jet.lang.psi.*; +import org.jetbrains.jet.lang.resolve.calls.model.ResolvedCall; + +public class InlineDescriptorUtils { + + public static boolean checkNonLocalReturnUsage(@NotNull DeclarationDescriptor fromFunction, @NotNull JetExpression startExpression, @NotNull BindingTrace trace) { + PsiElement containingFunction = PsiTreeUtil.getParentOfType(startExpression, JetClassOrObject.class, JetDeclarationWithBody.class); + if (containingFunction == null) { + return false; + } + + DeclarationDescriptor containingFunctionDescriptor = trace.get(BindingContext.DECLARATION_TO_DESCRIPTOR, containingFunction); + if (containingFunctionDescriptor == null) { + return false; + } + + BindingContext bindingContext = trace.getBindingContext(); + + while (containingFunction instanceof JetFunctionLiteral && fromFunction != containingFunctionDescriptor) { + //JetFunctionLiteralExpression + containingFunction = containingFunction.getParent(); + boolean isInlinedLambda = false; + JetExpression call = JetPsiUtil.getParentCallIfPresent((JetFunctionLiteralExpression) containingFunction); + if (call != null) { + //TODO: ask sveta + ResolvedCall resolvedCall = bindingContext.get(BindingContext.RESOLVED_CALL, JetPsiUtil.getCalleeExpressionIfAny(call)); + CallableDescriptor resultingDescriptor = resolvedCall == null ? null : resolvedCall.getResultingDescriptor(); + if (resultingDescriptor instanceof SimpleFunctionDescriptor) { + isInlinedLambda = ((SimpleFunctionDescriptor) resultingDescriptor).getInlineStrategy().isInline(); + } + } + if (!isInlinedLambda) { + return false; + } + + containingFunctionDescriptor = getContainingClassOrFunctionDescriptor(containingFunctionDescriptor, true); + + containingFunction = containingFunctionDescriptor != null + ? BindingContextUtils.descriptorToDeclaration(bindingContext, containingFunctionDescriptor) + : null; + } + + return fromFunction == containingFunctionDescriptor; + } + + @Nullable + public static DeclarationDescriptor getContainingClassOrFunctionDescriptor(@NotNull DeclarationDescriptor descriptor, boolean strict) { + DeclarationDescriptor currentDescriptor = strict ? descriptor.getContainingDeclaration() : descriptor; + while (currentDescriptor != null) { + if (currentDescriptor instanceof FunctionDescriptor || currentDescriptor instanceof ClassDescriptor) { + return currentDescriptor; + } + currentDescriptor = currentDescriptor.getContainingDeclaration(); + } + + return null; + } +} diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/resolve/calls/InlineCallResolverExtension.java b/compiler/frontend/src/org/jetbrains/jet/lang/resolve/calls/InlineCallResolverExtension.java index ac40516e7c6..0c8f9b90c4a 100644 --- a/compiler/frontend/src/org/jetbrains/jet/lang/resolve/calls/InlineCallResolverExtension.java +++ b/compiler/frontend/src/org/jetbrains/jet/lang/resolve/calls/InlineCallResolverExtension.java @@ -17,6 +17,7 @@ package org.jetbrains.jet.lang.resolve.calls; import com.intellij.psi.PsiElement; +import com.intellij.psi.util.PsiTreeUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.jet.lang.descriptors.*; @@ -24,11 +25,9 @@ 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.DescriptorUtils; +import org.jetbrains.jet.lang.resolve.InlineDescriptorUtils; import org.jetbrains.jet.lang.resolve.calls.context.BasicCallResolutionContext; -import org.jetbrains.jet.lang.resolve.calls.model.DefaultValueArgument; -import org.jetbrains.jet.lang.resolve.calls.model.ResolvedCall; -import org.jetbrains.jet.lang.resolve.calls.model.ResolvedValueArgument; -import org.jetbrains.jet.lang.resolve.calls.model.VarargValueArgument; +import org.jetbrains.jet.lang.resolve.calls.model.*; import org.jetbrains.jet.lang.resolve.scopes.receivers.ExpressionReceiver; import org.jetbrains.jet.lang.resolve.scopes.receivers.ExtensionReceiver; import org.jetbrains.jet.lang.resolve.scopes.receivers.ReceiverValue; @@ -39,6 +38,7 @@ import org.jetbrains.jet.lexer.JetToken; import org.jetbrains.jet.lexer.JetTokens; import java.util.HashSet; +import java.util.Map; import java.util.Set; public class InlineCallResolverExtension implements CallResolverExtension { @@ -82,69 +82,65 @@ public class InlineCallResolverExtension implements CallResolverExtension { checkCallWithReceiver(context, targetDescriptor, resolvedCall.getReceiverArgument(), expression); if (inlinableParameters.contains(targetDescriptor)) { - if (!couldAccessVariable(expression)) { + if (!isInsideCall(expression)) { context.trace.report(Errors.USAGE_IS_NOT_INLINABLE.on(expression, expression, descriptor)); } } - for (ResolvedValueArgument value : resolvedCall.getValueArguments().values()) { + for (Map.Entry entry : resolvedCall.getValueArguments().entrySet()) { + ResolvedValueArgument value = entry.getValue(); + ValueParameterDescriptor valueDescriptor = entry.getKey(); if (!(value instanceof DefaultValueArgument)) { for (ValueArgument argument : value.getArguments()) { - checkValueParameter(context, targetDescriptor, argument, value instanceof VarargValueArgument); + checkValueParameter(context, targetDescriptor, argument, valueDescriptor); } } } checkVisibility(targetDescriptor, expression, context); - checkRecursion(targetDescriptor, expression, context); + checkRecursion(context, targetDescriptor, expression); } - private static boolean couldAccessVariable(JetExpression expression) { - PsiElement parent = expression.getParent(); - while (parent != null) { - if (parent instanceof JetValueArgument || - parent instanceof JetBinaryExpression || - parent instanceof JetUnaryExpression || - parent instanceof JetLabeledExpression || - parent instanceof JetDotQualifiedExpression || - parent instanceof JetCallExpression || - parent instanceof JetArrayAccessExpression || - parent instanceof JetMultiDeclaration) { - - if (parent instanceof JetLabeledExpression) { - parent = parent.getParent(); - continue; - } - else if (parent instanceof JetBinaryExpression) { - JetToken token = JetPsiUtil.getOperationToken((JetOperationExpression) parent); - if (token == JetTokens.EQ || token == JetTokens.ANDAND || token == JetTokens.OROR) { - //assignment - return false; - } - } - - //check that it's in inlineable call would be in resolve call of parent - return true; - } - else if (parent instanceof JetParenthesizedExpression || parent instanceof JetBinaryExpressionWithTypeRHS) { - parent = parent.getParent(); - } - else { + private static boolean isInsideCall(JetExpression expression) { + JetElement parent = JetPsiUtil.getParentCallIfPresent(expression); + if (parent instanceof JetBinaryExpression) { + JetToken token = JetPsiUtil.getOperationToken((JetOperationExpression) parent); + if (token == JetTokens.EQ || token == JetTokens.ANDAND || token == JetTokens.OROR) { + //assignment return false; } } - return false; + + return parent != null; } - private void checkValueParameter(BasicCallResolutionContext context, CallableDescriptor targetDescriptor, ValueArgument argument, boolean isVararg) { - JetExpression jetExpression = argument.getArgumentExpression(); - if (jetExpression == null) { + + + private void checkValueParameter( + @NotNull BasicCallResolutionContext context, + @NotNull CallableDescriptor targetDescriptor, + @NotNull ValueArgument targetArgument, + @NotNull ValueParameterDescriptor targetParameterDescriptor + ) { + JetExpression argumentExpression = targetArgument.getArgumentExpression(); + if (argumentExpression == null) { return; } - CallableDescriptor varDescriptor = getDescriptor(context, jetExpression); + CallableDescriptor argumentCallee = getCalleeDescriptor(context, argumentExpression, false); - if (varDescriptor != null && inlinableParameters.contains(varDescriptor)) { - checkFunctionCall(context, targetDescriptor, jetExpression, isVararg); + if (argumentCallee != null && inlinableParameters.contains(argumentCallee)) { + boolean isTargetInlineFunction = targetDescriptor instanceof SimpleFunctionDescriptor && + ((SimpleFunctionDescriptor) targetDescriptor).getInlineStrategy().isInline(); + + if (!isTargetInlineFunction || !isInlinableParameter(targetParameterDescriptor)) { + context.trace.report(Errors.USAGE_IS_NOT_INLINABLE.on(argumentExpression, argumentExpression, descriptor)); + } else { + if (allowsNonLocalReturns(argumentCallee) && !allowsNonLocalReturns(targetParameterDescriptor)) { + context.trace.report(Errors.NON_LOCAL_RETURN_NOT_ALLOWED.on(argumentExpression, argumentExpression, argumentCallee, descriptor)); + } else { + checkNonLocalReturn(context, argumentCallee, argumentExpression); + } + } } } @@ -160,7 +156,7 @@ public class InlineCallResolverExtension implements CallResolverExtension { JetExpression receiverExpression = null; if (receiver instanceof ExpressionReceiver) { receiverExpression = ((ExpressionReceiver) receiver).getExpression(); - varDescriptor = getDescriptor(context, receiverExpression); + varDescriptor = getCalleeDescriptor(context, receiverExpression, true); } else if (receiver instanceof ExtensionReceiver) { ExtensionReceiver extensionReceiver = (ExtensionReceiver) receiver; @@ -174,35 +170,42 @@ public class InlineCallResolverExtension implements CallResolverExtension { if (inlinableParameters.contains(varDescriptor)) { //check that it's invoke or inlinable extension - checkFunctionCall(context, targetDescriptor, receiverExpression, false); + checkLambdaInvokeOrExtensionCall(context, varDescriptor, targetDescriptor, receiverExpression); } } @Nullable - private static CallableDescriptor getDescriptor( + private static CallableDescriptor getCalleeDescriptor( @NotNull BasicCallResolutionContext context, - @NotNull JetExpression expression + @NotNull JetExpression expression, + boolean unwrapVariableAsFunction ) { + // todo ask sveta ResolvedCall thisCall = context.trace.get(BindingContext.RESOLVED_CALL, expression); + if (unwrapVariableAsFunction && thisCall instanceof VariableAsFunctionResolvedCall) { + return ((VariableAsFunctionResolvedCall) thisCall).getVariableCall().getResultingDescriptor(); + } return thisCall != null ? thisCall.getResultingDescriptor() : null; } - private void checkFunctionCall( - BasicCallResolutionContext context, - CallableDescriptor targetDescriptor, - JetExpression receiverExpresssion, - boolean isVararg + private void checkLambdaInvokeOrExtensionCall( + @NotNull BasicCallResolutionContext context, + @NotNull CallableDescriptor lambdaDescriptor, + @NotNull CallableDescriptor callDescriptor, + @NotNull JetExpression receiverExpresssion ) { - boolean inlinableCall = isInvokeOrInlineExtension(targetDescriptor); - if (!inlinableCall || isVararg) { + boolean inlinableCall = isInvokeOrInlineExtension(callDescriptor); + if (!inlinableCall) { context.trace.report(Errors.USAGE_IS_NOT_INLINABLE.on(receiverExpresssion, receiverExpresssion, descriptor)); + } else { + checkNonLocalReturn(context, lambdaDescriptor, receiverExpresssion); } } public void checkRecursion( + @NotNull BasicCallResolutionContext context, @NotNull CallableDescriptor targetDescriptor, - @NotNull JetElement expression, - @NotNull BasicCallResolutionContext context + @NotNull JetElement expression ) { if (targetDescriptor.getOriginal() == descriptor) { context.trace.report(Errors.RECURSION_IN_INLINE.on(expression, expression, descriptor)); @@ -248,4 +251,34 @@ public class InlineCallResolverExtension implements CallResolverExtension { } return true; } + + private void checkNonLocalReturn( + @NotNull BasicCallResolutionContext context, + @NotNull CallableDescriptor inlinableParameterDescriptor, + @NotNull JetExpression parameterUsage + ) { + if (!allowsNonLocalReturns(inlinableParameterDescriptor)) return; + + if (!InlineDescriptorUtils.checkNonLocalReturnUsage(descriptor, parameterUsage, context.trace)) { + context.trace.report(Errors.NON_LOCAL_RETURN_NOT_ALLOWED.on(parameterUsage, parameterUsage, inlinableParameterDescriptor, descriptor)); + } + } + + @Nullable + public static PsiElement getDeclaration(JetExpression expression) { + do { + expression = PsiTreeUtil.getParentOfType(expression, JetDeclaration.class); + } while (expression instanceof JetMultiDeclaration || expression instanceof JetProperty); + return expression; + } + + private static boolean allowsNonLocalReturns(CallableDescriptor lambdaDescriptor) { + if (lambdaDescriptor instanceof ValueParameterDescriptor) { + if (InlineUtil.hasOnlyLocalReturn((ValueParameterDescriptor) lambdaDescriptor)) { + //annotated + return false; + } + } + return true; + } } diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/types/expressions/ControlStructureTypingVisitor.java b/compiler/frontend/src/org/jetbrains/jet/lang/types/expressions/ControlStructureTypingVisitor.java index a2fa031e03b..9d7b37af0ac 100644 --- a/compiler/frontend/src/org/jetbrains/jet/lang/types/expressions/ControlStructureTypingVisitor.java +++ b/compiler/frontend/src/org/jetbrains/jet/lang/types/expressions/ControlStructureTypingVisitor.java @@ -22,16 +22,10 @@ import com.intellij.psi.PsiElement; import com.intellij.psi.util.PsiTreeUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.jetbrains.jet.lang.descriptors.DeclarationDescriptor; -import org.jetbrains.jet.lang.descriptors.FunctionDescriptor; -import org.jetbrains.jet.lang.descriptors.SimpleFunctionDescriptor; -import org.jetbrains.jet.lang.descriptors.VariableDescriptor; +import org.jetbrains.jet.lang.descriptors.*; 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.DescriptorResolver; -import org.jetbrains.jet.lang.resolve.DescriptorUtils; +import org.jetbrains.jet.lang.resolve.*; import org.jetbrains.jet.lang.resolve.calls.autocasts.DataFlowInfo; import org.jetbrains.jet.lang.resolve.calls.model.MutableDataFlowInfoForArguments; import org.jetbrains.jet.lang.resolve.calls.model.ResolvedCall; @@ -451,27 +445,24 @@ public class ControlStructureTypingVisitor extends ExpressionTypingVisitor { // In a default value for parameter context.trace.report(RETURN_NOT_ALLOWED.on(expression)); } - assert parentDeclaration != null; - DeclarationDescriptor declarationDescriptor = context.trace.get(DECLARATION_TO_DESCRIPTOR, parentDeclaration); - FunctionDescriptor containingFunctionDescriptor = DescriptorUtils.getParentOfType(declarationDescriptor, FunctionDescriptor.class, false); if (expression.getTargetLabel() == null) { - if (containingFunctionDescriptor != null) { - PsiElement containingFunction = BindingContextUtils.callableDescriptorToDeclaration(context.trace.getBindingContext(), containingFunctionDescriptor); - assert containingFunction != null; - if (containingFunction instanceof JetFunctionLiteral) { - Pair result = BindingContextUtils - .getLambdaContainingFunction(containingFunctionDescriptor, context.trace.getBindingContext()); - containingFunctionDescriptor = result.getFirst(); - containingFunction = result.getSecond(); + assert parentDeclaration != null; + DeclarationDescriptor declarationDescriptor = context.trace.get(DECLARATION_TO_DESCRIPTOR, parentDeclaration); + Pair containingFunInfo = + BindingContextUtils.getContainingFunctionSkipFunctionLiterals(context.trace.getBindingContext(), declarationDescriptor, + false); + FunctionDescriptor containingFunctionDescriptor = containingFunInfo.getFirst(); + if (containingFunctionDescriptor != null) { + if (!InlineDescriptorUtils.checkNonLocalReturnUsage(containingFunctionDescriptor, expression, context.trace) || + containingFunctionDescriptor instanceof ConstructorDescriptor) { // Unqualified, in a function literal context.trace.report(RETURN_NOT_ALLOWED.on(expression)); resultType = ErrorUtils.createErrorType(RETURN_NOT_ALLOWED_MESSAGE); } - if (containingFunctionDescriptor != null) { - expectedType = getFunctionExpectedReturnType(containingFunctionDescriptor, (JetElement) containingFunction); - } + + expectedType = getFunctionExpectedReturnType(containingFunctionDescriptor, (JetElement) containingFunInfo.getSecond()); } else { // Outside a function @@ -483,7 +474,7 @@ public class ControlStructureTypingVisitor extends ExpressionTypingVisitor { SimpleFunctionDescriptor functionDescriptor = context.trace.get(FUNCTION, labelTargetElement); if (functionDescriptor != null) { expectedType = getFunctionExpectedReturnType(functionDescriptor, labelTargetElement); - if (functionDescriptor != containingFunctionDescriptor) { + if (!InlineDescriptorUtils.checkNonLocalReturnUsage(functionDescriptor, expression, context.trace)) { // Qualified, non-local context.trace.report(RETURN_NOT_ALLOWED.on(expression)); resultType = ErrorUtils.createErrorType(RETURN_NOT_ALLOWED_MESSAGE); diff --git a/compiler/testData/diagnostics/tests/inline/binaryExpressions/assignment.kt b/compiler/testData/diagnostics/tests/inline/binaryExpressions/assignment.kt index 293f864afc5..f10bd226d96 100644 --- a/compiler/testData/diagnostics/tests/inline/binaryExpressions/assignment.kt +++ b/compiler/testData/diagnostics/tests/inline/binaryExpressions/assignment.kt @@ -1,4 +1,4 @@ -// !DIAGNOSTICS: -UNUSED_EXPRESSION -UNUSED_PARAMETER -UNUSED_VARIABLE -NOTHING_TO_INLINE -ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE -UNUSED_VALUE +// !DIAGNOSTICS: -UNUSED_EXPRESSION -UNUSED_PARAMETER -UNUSED_VARIABLE -NOTHING_TO_INLINE -ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE -UNUSED_VALUE -ONLY_LOCAL_RETURN fun Function1.minusAssign(p: Function1) {} diff --git a/compiler/testData/diagnostics/tests/inline/fromInlineToNoInline.kt b/compiler/testData/diagnostics/tests/inline/fromInlineToNoInline.kt new file mode 100644 index 00000000000..1b9df97e03b --- /dev/null +++ b/compiler/testData/diagnostics/tests/inline/fromInlineToNoInline.kt @@ -0,0 +1,7 @@ +inline fun onlyLocal(p: () -> R) { + inlineAll(p) +} + +inline fun inlineAll(noinline p: () -> R) { + p() +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/inline/nonLocalReturns/anonymousObjects.kt b/compiler/testData/diagnostics/tests/inline/nonLocalReturns/anonymousObjects.kt new file mode 100644 index 00000000000..9b68c80e041 --- /dev/null +++ b/compiler/testData/diagnostics/tests/inline/nonLocalReturns/anonymousObjects.kt @@ -0,0 +1,24 @@ +// !DIAGNOSTICS: -UNUSED_EXPRESSION -UNUSED_PARAMETER -UNUSED_VARIABLE -NOTHING_TO_INLINE +import kotlin.InlineOption.* + +inline fun inlineFunOnlyLocal(inlineOptions(ONLY_LOCAL_RETURN)p: () -> R) { + val s = object { + + val z = p() + + fun a() { + p() + } + } +} + +inline fun inlineFun(p: () -> R) { + val s = object { + + val z = p() + + fun a() { + p() + } + } +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/inline/nonLocalReturns/anonymousObjectsNested.kt b/compiler/testData/diagnostics/tests/inline/nonLocalReturns/anonymousObjectsNested.kt new file mode 100644 index 00000000000..1ef06fa3ef2 --- /dev/null +++ b/compiler/testData/diagnostics/tests/inline/nonLocalReturns/anonymousObjectsNested.kt @@ -0,0 +1,49 @@ +// !DIAGNOSTICS: -UNUSED_EXPRESSION -UNUSED_PARAMETER -UNUSED_VARIABLE -NOTHING_TO_INLINE +import kotlin.InlineOption.* + +inline fun inlineFunOnlyLocal(inlineOptions(ONLY_LOCAL_RETURN)p: () -> R) { + val s = object { + + val z = p(); + + { + doCall { + p() + } + } + + fun a() { + doCall { + p() + } + + p() + } + } +} + +inline fun inlineFun(p: () -> R) { + val s = object { + + val z = p(); + + { + doCall { + p() + } + } + + + fun a() { + doCall { + p() + } + + p() + } + } +} + +inline fun doCall(p: () -> R) { + p() +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/inline/nonLocalReturns/explicitReturnType.kt b/compiler/testData/diagnostics/tests/inline/nonLocalReturns/explicitReturnType.kt new file mode 100644 index 00000000000..e61c5913247 --- /dev/null +++ b/compiler/testData/diagnostics/tests/inline/nonLocalReturns/explicitReturnType.kt @@ -0,0 +1,43 @@ +fun inlineCallExplicitError(): String { + inlineFun @lamba { + if (true) { + return@lamba 2 + } + 1 + } + + return "x" +} + +fun inlineCall(): String { + inlineFun @lamba { + (): Int -> + if (true) { + return@lamba 2 + } + 1 + } + + return "x" +} + +inline fun inlineFun(s: () -> Int) { + s() +} + + +fun noInlineCall(): String { + noInline @lambda { + (): Int -> + if (true) { + return@lambda 2 + } + 1 + } + return "x" +} + + +fun noInline(s: () -> Int) { + s() +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/inline/nonLocalReturns/fromOnlyLocal.kt b/compiler/testData/diagnostics/tests/inline/nonLocalReturns/fromOnlyLocal.kt new file mode 100644 index 00000000000..b4496e3aaad --- /dev/null +++ b/compiler/testData/diagnostics/tests/inline/nonLocalReturns/fromOnlyLocal.kt @@ -0,0 +1,9 @@ +import kotlin.InlineOption.* + +inline fun onlyLocal(inlineOptions(ONLY_LOCAL_RETURN)p: () -> R) { + inlineAll(p) +} + +inline fun inlineAll(p: () -> R) { + p() +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/inline/nonLocalReturns/inlineLambda.kt b/compiler/testData/diagnostics/tests/inline/nonLocalReturns/inlineLambda.kt new file mode 100644 index 00000000000..575faa44a26 --- /dev/null +++ b/compiler/testData/diagnostics/tests/inline/nonLocalReturns/inlineLambda.kt @@ -0,0 +1,17 @@ +import kotlin.InlineOption.* + +inline fun inlineFunWithAnnotation(inlineOptions(ONLY_LOCAL_RETURN) p: () -> R) { + inlineFun { + p() + } +} + +inline fun inlineFun2(p: () -> R) { + inlineFun { + p() + } +} + +inline fun inlineFun(p: () -> R) { + p() +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/inline/nonLocalReturns/labeledReturn.kt b/compiler/testData/diagnostics/tests/inline/nonLocalReturns/labeledReturn.kt new file mode 100644 index 00000000000..18631aa12c7 --- /dev/null +++ b/compiler/testData/diagnostics/tests/inline/nonLocalReturns/labeledReturn.kt @@ -0,0 +1,31 @@ +fun inlineCall(): String { + inlineFun { + if (true) { + return@inlineCall "" + } + 1 + } + + return "x" +} + +inline fun inlineFun(s: ()->Int) { + s() +} + + +fun noInlineCall(): String { + noInline { + if (true) { + return@noInlineCall "" + } + 1 + } + + return "x" +} + + +fun noInline(s: ()->Int) { + s() +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/inline/nonLocalReturns/localFun.kt b/compiler/testData/diagnostics/tests/inline/nonLocalReturns/localFun.kt new file mode 100644 index 00000000000..0bc05e4d922 --- /dev/null +++ b/compiler/testData/diagnostics/tests/inline/nonLocalReturns/localFun.kt @@ -0,0 +1,16 @@ +// !DIAGNOSTICS: -UNUSED_EXPRESSION -UNUSED_PARAMETER -UNUSED_VARIABLE -NOTHING_TO_INLINE +import kotlin.InlineOption.* + +inline fun inlineFunOnlyLocal(inlineOptions(ONLY_LOCAL_RETURN)p: () -> R) { + fun a() { + val z = p() + } + a() +} + +inline fun inlineFun(p: () -> R) { + fun a() { + p() + } + a() +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/inline/nonLocalReturns/nestedNonLocals.kt b/compiler/testData/diagnostics/tests/inline/nonLocalReturns/nestedNonLocals.kt new file mode 100644 index 00000000000..bfa7e193607 --- /dev/null +++ b/compiler/testData/diagnostics/tests/inline/nonLocalReturns/nestedNonLocals.kt @@ -0,0 +1,43 @@ +import Kind.EXT_RETURN +import Kind.GLOBAL_RETURN + +enum class Kind { + LOCAL + EXT_RETURN + GLOBAL_RETURN +} + +class Internal(val value: String) + +class External(val value: String) + +class Global(val value: String) + +fun test1(intKind: Kind, extKind: Kind): Global { + + var externalResult = doCall @ext { + () : External -> + + val internalResult = doCall @int { + () : Internal -> + if (intKind == Kind.LOCAL) { + return@test1 Global("internal to global") + } else if (intKind == EXT_RETURN) { + return@ext External("internal to external") + } + return@int Internal("internal to local") + } + + if (extKind == GLOBAL_RETURN || extKind == EXT_RETURN) { + return Global("external to global") + } + + External(internalResult.value + " to local"); + } + + return Global(externalResult.value + " to exit") +} + +public inline fun doCall(block: ()-> R) : R { + return block() +} diff --git a/compiler/testData/diagnostics/tests/inline/nonLocalReturns/noInlineLambda.kt b/compiler/testData/diagnostics/tests/inline/nonLocalReturns/noInlineLambda.kt new file mode 100644 index 00000000000..bf3b350c7d8 --- /dev/null +++ b/compiler/testData/diagnostics/tests/inline/nonLocalReturns/noInlineLambda.kt @@ -0,0 +1,14 @@ +// !DIAGNOSTICS: -UNUSED_EXPRESSION -UNUSED_PARAMETER -UNUSED_VARIABLE -NOTHING_TO_INLINE +import kotlin.InlineOption.* + +inline fun inlineFunOnlyLocal(inlineOptions(ONLY_LOCAL_RETURN)p: () -> R) { + { + p() + }() +} + +inline fun inlineFun(p: () -> R) { + { + p() + }() +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/inline/nonLocalReturns/nonInlinedClass.kt b/compiler/testData/diagnostics/tests/inline/nonLocalReturns/nonInlinedClass.kt new file mode 100644 index 00000000000..a382798aca5 --- /dev/null +++ b/compiler/testData/diagnostics/tests/inline/nonLocalReturns/nonInlinedClass.kt @@ -0,0 +1,24 @@ +// !DIAGNOSTICS: -UNUSED_EXPRESSION -UNUSED_PARAMETER -UNUSED_VARIABLE -NOTHING_TO_INLINE +import kotlin.InlineOption.* + +inline fun inlineFunOnlyLocal(inlineOptions(ONLY_LOCAL_RETURN)p: () -> R) { + class A { + + val z = p() + + fun a() { + p() + } + } +} + +inline fun inlineFun(p: () -> R) { + class A { + + val z = p() + + fun a() { + p() + } + } +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/inline/nonLocalReturns/propertyAccessorsAndConstructor.kt b/compiler/testData/diagnostics/tests/inline/nonLocalReturns/propertyAccessorsAndConstructor.kt new file mode 100644 index 00000000000..b9134143b31 --- /dev/null +++ b/compiler/testData/diagnostics/tests/inline/nonLocalReturns/propertyAccessorsAndConstructor.kt @@ -0,0 +1,24 @@ +inline fun doCall(p: () -> R) { + p() +} + +inline fun doCallInt(p: () -> R): R { + return p() +} + +class A { + var result: Int = doCallInt { return this }; + + var field: Int + get() { + doCall { return 1 } + return 2 + } + set(v: Int) { + doCall { + result = v / 2 + return + } + result = v + } +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/inline/nonLocalReturns/toOnlyLocal.kt b/compiler/testData/diagnostics/tests/inline/nonLocalReturns/toOnlyLocal.kt new file mode 100644 index 00000000000..71b69583e83 --- /dev/null +++ b/compiler/testData/diagnostics/tests/inline/nonLocalReturns/toOnlyLocal.kt @@ -0,0 +1,9 @@ +import kotlin.InlineOption.* + +inline fun toOnlyLocal(inlineOptions(ONLY_LOCAL_RETURN) p: () -> R) { + p() +} + +inline fun inlineAll(p: () -> R) { + toOnlyLocal(p) +} diff --git a/compiler/tests/org/jetbrains/jet/checkers/JetDiagnosticsTestGenerated.java b/compiler/tests/org/jetbrains/jet/checkers/JetDiagnosticsTestGenerated.java index 01bce137e4e..f0c45efbf0c 100644 --- a/compiler/tests/org/jetbrains/jet/checkers/JetDiagnosticsTestGenerated.java +++ b/compiler/tests/org/jetbrains/jet/checkers/JetDiagnosticsTestGenerated.java @@ -4464,7 +4464,7 @@ public class JetDiagnosticsTestGenerated extends AbstractJetDiagnosticsTest { } @TestMetadata("compiler/testData/diagnostics/tests/inline") - @InnerTestClasses({Inline.BinaryExpressions.class, Inline.NonPublicMember.class, Inline.Regressions.class, Inline.UnaryExpressions.class}) + @InnerTestClasses({Inline.BinaryExpressions.class, Inline.NonLocalReturns.class, Inline.NonPublicMember.class, Inline.Regressions.class, Inline.UnaryExpressions.class}) public static class Inline extends AbstractJetDiagnosticsTest { public void testAllFilesPresentInInline() throws Exception { JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), "org.jetbrains.jet.generators.tests.TestsPackage", new File("compiler/testData/diagnostics/tests/inline"), Pattern.compile("^(.+)\\.kt$"), true); @@ -4500,6 +4500,11 @@ public class JetDiagnosticsTestGenerated extends AbstractJetDiagnosticsTest { doTest("compiler/testData/diagnostics/tests/inline/extensionOnFunction.kt"); } + @TestMetadata("fromInlineToNoInline.kt") + public void testFromInlineToNoInline() throws Exception { + doTest("compiler/testData/diagnostics/tests/inline/fromInlineToNoInline.kt"); + } + @TestMetadata("functions.kt") public void testFunctions() throws Exception { doTest("compiler/testData/diagnostics/tests/inline/functions.kt"); @@ -4628,6 +4633,74 @@ public class JetDiagnosticsTestGenerated extends AbstractJetDiagnosticsTest { } + @TestMetadata("compiler/testData/diagnostics/tests/inline/nonLocalReturns") + public static class NonLocalReturns extends AbstractJetDiagnosticsTest { + public void testAllFilesPresentInNonLocalReturns() throws Exception { + JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), "org.jetbrains.jet.generators.tests.TestsPackage", new File("compiler/testData/diagnostics/tests/inline/nonLocalReturns"), Pattern.compile("^(.+)\\.kt$"), true); + } + + @TestMetadata("anonymousObjects.kt") + public void testAnonymousObjects() throws Exception { + doTest("compiler/testData/diagnostics/tests/inline/nonLocalReturns/anonymousObjects.kt"); + } + + @TestMetadata("anonymousObjectsNested.kt") + public void testAnonymousObjectsNested() throws Exception { + doTest("compiler/testData/diagnostics/tests/inline/nonLocalReturns/anonymousObjectsNested.kt"); + } + + @TestMetadata("explicitReturnType.kt") + public void testExplicitReturnType() throws Exception { + doTest("compiler/testData/diagnostics/tests/inline/nonLocalReturns/explicitReturnType.kt"); + } + + @TestMetadata("fromOnlyLocal.kt") + public void testFromOnlyLocal() throws Exception { + doTest("compiler/testData/diagnostics/tests/inline/nonLocalReturns/fromOnlyLocal.kt"); + } + + @TestMetadata("inlineLambda.kt") + public void testInlineLambda() throws Exception { + doTest("compiler/testData/diagnostics/tests/inline/nonLocalReturns/inlineLambda.kt"); + } + + @TestMetadata("labeledReturn.kt") + public void testLabeledReturn() throws Exception { + doTest("compiler/testData/diagnostics/tests/inline/nonLocalReturns/labeledReturn.kt"); + } + + @TestMetadata("localFun.kt") + public void testLocalFun() throws Exception { + doTest("compiler/testData/diagnostics/tests/inline/nonLocalReturns/localFun.kt"); + } + + @TestMetadata("nestedNonLocals.kt") + public void testNestedNonLocals() throws Exception { + doTest("compiler/testData/diagnostics/tests/inline/nonLocalReturns/nestedNonLocals.kt"); + } + + @TestMetadata("noInlineLambda.kt") + public void testNoInlineLambda() throws Exception { + doTest("compiler/testData/diagnostics/tests/inline/nonLocalReturns/noInlineLambda.kt"); + } + + @TestMetadata("nonInlinedClass.kt") + public void testNonInlinedClass() throws Exception { + doTest("compiler/testData/diagnostics/tests/inline/nonLocalReturns/nonInlinedClass.kt"); + } + + @TestMetadata("propertyAccessorsAndConstructor.kt") + public void testPropertyAccessorsAndConstructor() throws Exception { + doTest("compiler/testData/diagnostics/tests/inline/nonLocalReturns/propertyAccessorsAndConstructor.kt"); + } + + @TestMetadata("toOnlyLocal.kt") + public void testToOnlyLocal() throws Exception { + doTest("compiler/testData/diagnostics/tests/inline/nonLocalReturns/toOnlyLocal.kt"); + } + + } + @TestMetadata("compiler/testData/diagnostics/tests/inline/nonPublicMember") public static class NonPublicMember extends AbstractJetDiagnosticsTest { public void testAllFilesPresentInNonPublicMember() throws Exception { @@ -4706,6 +4779,7 @@ public class JetDiagnosticsTestGenerated extends AbstractJetDiagnosticsTest { TestSuite suite = new TestSuite("Inline"); suite.addTestSuite(Inline.class); suite.addTestSuite(BinaryExpressions.class); + suite.addTestSuite(NonLocalReturns.class); suite.addTestSuite(NonPublicMember.class); suite.addTestSuite(Regressions.class); suite.addTestSuite(UnaryExpressions.class); diff --git a/core/builtins/src/kotlin/Inline.kt b/core/builtins/src/kotlin/Inline.kt index 05906f326a4..d9de880b061 100644 --- a/core/builtins/src/kotlin/Inline.kt +++ b/core/builtins/src/kotlin/Inline.kt @@ -24,3 +24,11 @@ public enum class InlineStrategy { AS_FUNCTION IN_PLACE } + + +public annotation class inlineOptions(vararg val value: InlineOption) + +public enum class InlineOption { + LOCAL_CONTINUE_AND_BREAK + ONLY_LOCAL_RETURN +} \ No newline at end of file diff --git a/core/descriptors/src/org/jetbrains/jet/lang/types/lang/InlineOption.java b/core/descriptors/src/org/jetbrains/jet/lang/types/lang/InlineOption.java new file mode 100644 index 00000000000..7d2601fd513 --- /dev/null +++ b/core/descriptors/src/org/jetbrains/jet/lang/types/lang/InlineOption.java @@ -0,0 +1,22 @@ +/* + * Copyright 2010-2014 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.types.lang; + +public enum InlineOption { + LOCAL_CONTINUE_AND_BREAK, + ONLY_LOCAL_RETURN +} diff --git a/core/descriptors/src/org/jetbrains/jet/lang/types/lang/InlineUtil.java b/core/descriptors/src/org/jetbrains/jet/lang/types/lang/InlineUtil.java index 70389518aa1..8badc9e74b1 100644 --- a/core/descriptors/src/org/jetbrains/jet/lang/types/lang/InlineUtil.java +++ b/core/descriptors/src/org/jetbrains/jet/lang/types/lang/InlineUtil.java @@ -23,9 +23,12 @@ import org.jetbrains.jet.lang.descriptors.ClassDescriptor; import org.jetbrains.jet.lang.descriptors.ValueParameterDescriptor; import org.jetbrains.jet.lang.descriptors.annotations.AnnotationDescriptor; import org.jetbrains.jet.lang.descriptors.annotations.Annotations; +import org.jetbrains.jet.lang.resolve.constants.ArrayValue; import org.jetbrains.jet.lang.resolve.constants.CompileTimeConstant; import org.jetbrains.jet.lang.resolve.constants.EnumValue; +import java.util.List; + public class InlineUtil { public static boolean hasNoinlineAnnotation(@NotNull CallableDescriptor valueParameterDescriptor) { @@ -68,6 +71,40 @@ public class InlineUtil { return null; } + public static boolean hasOnlyLocalContinueAndBreak(@NotNull ValueParameterDescriptor descriptor) { + return hasInlineOption(descriptor, InlineOption.LOCAL_CONTINUE_AND_BREAK); + } + public static boolean hasOnlyLocalReturn(@NotNull ValueParameterDescriptor descriptor) { + return hasInlineOption(descriptor, InlineOption.ONLY_LOCAL_RETURN); + } + + private static boolean hasInlineOption(@NotNull ValueParameterDescriptor descriptor, @NotNull InlineOption option) { + KotlinBuiltIns builtIns = KotlinBuiltIns.getInstance(); + ClassDescriptor annotationClass = builtIns.getInlineOptionsClassAnnotation(); + AnnotationDescriptor optionsAnnotation = getAnnotation(descriptor.getAnnotations(), annotationClass); + + if (optionsAnnotation != null) { + ValueParameterDescriptor parameterDescriptor = annotationClass.getConstructors().iterator().next().getValueParameters().get(0); + CompileTimeConstant argument = optionsAnnotation.getValueArgument(parameterDescriptor); + + if (argument == null) { + return false; + } else { + if (argument instanceof ArrayValue) { + List> values = ((ArrayValue) argument).getValue(); + + for (CompileTimeConstant value : values) { + if (value instanceof EnumValue) { + if (((EnumValue) value).getValue().getName().asString().equals(option.name())) { + return true; + } + } + } + } + } + } + return false; + } } diff --git a/core/descriptors/src/org/jetbrains/jet/lang/types/lang/KotlinBuiltIns.java b/core/descriptors/src/org/jetbrains/jet/lang/types/lang/KotlinBuiltIns.java index 4ff8aa08128..21eda3bea73 100644 --- a/core/descriptors/src/org/jetbrains/jet/lang/types/lang/KotlinBuiltIns.java +++ b/core/descriptors/src/org/jetbrains/jet/lang/types/lang/KotlinBuiltIns.java @@ -307,6 +307,11 @@ public class KotlinBuiltIns { return getBuiltInClassByName("inline"); } + @NotNull + public ClassDescriptor getInlineOptionsClassAnnotation() { + return getBuiltInClassByName("inlineOptions"); + } + @NotNull public ClassDescriptor getTailRecursiveAnnotationClass() { return getBuiltInClassByName("tailRecursive");