From 4138ac4e36b228a6e25b2fed7081d49e30194a85 Mon Sep 17 00:00:00 2001 From: Andrey Breslav Date: Wed, 21 Aug 2013 17:33:58 +0400 Subject: [PATCH] Support local returns in lambdas --- .../ClosureExpressionsTypingVisitor.java | 91 ++++++++++++++++--- .../expressions/ExpressionTypingServices.java | 14 +-- .../return/LocalAndNonLocalReturnInLambda.kt | 8 ++ .../LocalReturnExplicitLabelNoParens.kt | 2 +- .../return/LocalReturnExplicitLabelParens.kt | 4 +- .../return/LocalReturnInNestedFunction.kt | 11 +++ .../return/LocalReturnInNestedLambda.kt | 10 ++ .../return/LocalReturnNoCoercionToUnit.kt | 8 ++ .../return/LocalReturnUnit.kt | 8 ++ .../return/MixedReturnsFromLambda.kt | 13 +++ .../checkers/JetDiagnosticsTestGenerated.java | 30 ++++++ 11 files changed, 171 insertions(+), 28 deletions(-) create mode 100644 compiler/testData/diagnostics/tests/functionLiterals/return/LocalAndNonLocalReturnInLambda.kt create mode 100644 compiler/testData/diagnostics/tests/functionLiterals/return/LocalReturnInNestedFunction.kt create mode 100644 compiler/testData/diagnostics/tests/functionLiterals/return/LocalReturnInNestedLambda.kt create mode 100644 compiler/testData/diagnostics/tests/functionLiterals/return/LocalReturnNoCoercionToUnit.kt create mode 100644 compiler/testData/diagnostics/tests/functionLiterals/return/LocalReturnUnit.kt create mode 100644 compiler/testData/diagnostics/tests/functionLiterals/return/MixedReturnsFromLambda.kt diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/types/expressions/ClosureExpressionsTypingVisitor.java b/compiler/frontend/src/org/jetbrains/jet/lang/types/expressions/ClosureExpressionsTypingVisitor.java index 2e1786fcb33..770728bd5f4 100644 --- a/compiler/frontend/src/org/jetbrains/jet/lang/types/expressions/ClosureExpressionsTypingVisitor.java +++ b/compiler/frontend/src/org/jetbrains/jet/lang/types/expressions/ClosureExpressionsTypingVisitor.java @@ -18,6 +18,8 @@ package org.jetbrains.jet.lang.types.expressions; import com.google.common.collect.Lists; import com.intellij.psi.PsiElement; +import com.intellij.util.Function; +import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.jet.lang.descriptors.*; @@ -28,21 +30,20 @@ import org.jetbrains.jet.lang.resolve.*; import org.jetbrains.jet.lang.resolve.calls.CallResolverUtil; import org.jetbrains.jet.lang.resolve.name.Name; import org.jetbrains.jet.lang.resolve.scopes.JetScope; -import org.jetbrains.jet.lang.types.DeferredType; -import org.jetbrains.jet.lang.types.ErrorUtils; -import org.jetbrains.jet.lang.types.JetType; -import org.jetbrains.jet.lang.types.JetTypeInfo; +import org.jetbrains.jet.lang.types.*; import org.jetbrains.jet.lang.types.checker.JetTypeChecker; import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns; import org.jetbrains.jet.util.lazy.RecursionIntolerantLazyValueWithDefault; import org.jetbrains.jet.util.slicedmap.WritableSlice; +import java.util.Collection; import java.util.Collections; import java.util.List; import static org.jetbrains.jet.lang.diagnostics.Errors.*; import static org.jetbrains.jet.lang.resolve.BindingContext.*; import static org.jetbrains.jet.lang.types.TypeUtils.NO_EXPECTED_TYPE; +import static org.jetbrains.jet.lang.types.expressions.CoercionStrategy.COERCION_TO_UNIT; import static org.jetbrains.jet.lang.types.expressions.ExpressionTypingUtils.CANT_INFER_LAMBDA_PARAM_TYPE; public class ClosureExpressionsTypingVisitor extends ExpressionTypingVisitor { @@ -128,7 +129,8 @@ public class ClosureExpressionsTypingVisitor extends ExpressionTypingVisitor { AnonymousFunctionDescriptor functionDescriptor = new AnonymousFunctionDescriptor( context.scope.getContainingDeclaration(), Collections.emptyList(), CallableMemberDescriptor.Kind.DECLARATION); - List valueParameterDescriptors = createValueParameterDescriptors(context, functionLiteral, functionDescriptor, functionTypeExpected); + List valueParameterDescriptors = createValueParameterDescriptors(context, functionLiteral, + functionDescriptor, functionTypeExpected); JetType effectiveReceiverType; if (receiverTypeRef == null) { @@ -243,7 +245,8 @@ public class ClosureExpressionsTypingVisitor extends ExpressionTypingVisitor { @NotNull SimpleFunctionDescriptorImpl functionDescriptor, boolean functionTypeExpected ) { - TemporaryBindingTrace temporaryTrace = TemporaryBindingTrace.create(context.trace, "trace to resolve function literal expression", expression); + TemporaryBindingTrace temporaryTrace = TemporaryBindingTrace.create(context.trace, "trace to resolve function literal expression", + expression); JetType expectedReturnType = functionTypeExpected ? KotlinBuiltIns.getInstance().getReturnTypeFromFunctionType(context.expectedType) : null; JetType returnType = computeUnsafeReturnType(expression, context, functionDescriptor, temporaryTrace, expectedReturnType); @@ -276,19 +279,79 @@ public class ClosureExpressionsTypingVisitor extends ExpressionTypingVisitor { JetScope functionInnerScope = FunctionDescriptorUtil.getFunctionInnerScope(context.scope, functionDescriptor, context.trace); JetTypeReference returnTypeRef = functionLiteral.getReturnTypeRef(); + JetType declaredReturnType = null; if (returnTypeRef != null) { - JetType returnType = context.expressionTypingServices.getTypeResolver().resolveType(context.scope, returnTypeRef, context.trace, true); - context.expressionTypingServices.checkFunctionReturnType(expression.getFunctionLiteral(), context.replaceScope(functionInnerScope). - replaceExpectedType(returnType).replaceBindingTrace(temporaryTrace), temporaryTrace); + declaredReturnType = context.expressionTypingServices.getTypeResolver().resolveType(context.scope, returnTypeRef, context.trace, true); if (expectedReturnType != null) { - if (!JetTypeChecker.INSTANCE.isSubtypeOf(returnType, expectedReturnType)) { + if (!JetTypeChecker.INSTANCE.isSubtypeOf(declaredReturnType, expectedReturnType)) { temporaryTrace.report(EXPECTED_RETURN_TYPE_MISMATCH.on(returnTypeRef, expectedReturnType)); } } - return returnType; } - ExpressionTypingContext newContext = context.replaceExpectedType(expectedReturnType != null ? expectedReturnType : NO_EXPECTED_TYPE) - .replaceBindingTrace(temporaryTrace).replaceScope(functionInnerScope); - return context.expressionTypingServices.getBlockReturnedType(bodyExpression, CoercionStrategy.COERCION_TO_UNIT, newContext).getType(); + + // Type-check the body + ExpressionTypingContext newContext = context.replaceBindingTrace(temporaryTrace).replaceScope(functionInnerScope) + .replaceExpectedType(declaredReturnType != null + ? declaredReturnType + : (expectedReturnType != null ? expectedReturnType : NO_EXPECTED_TYPE)); + + JetType typeOfBodyExpression = context.expressionTypingServices.getBlockReturnedType(bodyExpression, COERCION_TO_UNIT, newContext).getType(); + + List returnedExpressionTypes = Lists.newArrayList(getTypesOfLocallyReturnedExpressions( + functionLiteral, temporaryTrace, collectReturns(bodyExpression))); + ContainerUtil.addIfNotNull(returnedExpressionTypes, typeOfBodyExpression); + + if (declaredReturnType != null) return declaredReturnType; + if (returnedExpressionTypes.isEmpty()) return null; + return CommonSupertypes.commonSupertype(returnedExpressionTypes); + } + + private static List getTypesOfLocallyReturnedExpressions( + final JetFunctionLiteral functionLiteral, + final TemporaryBindingTrace temporaryTrace, + Collection returnExpressions + ) { + return ContainerUtil.mapNotNull(returnExpressions, new Function() { + @Override + public JetType fun(JetReturnExpression returnExpression) { + JetSimpleNameExpression label = returnExpression.getTargetLabel(); + if (label == null) { + // No label => non-local return + return null; + } + + PsiElement labelTarget = temporaryTrace.get(BindingContext.LABEL_TARGET, label); + if (labelTarget != functionLiteral) { + // Either a local return of inner lambda/function or a non-local return + return null; + } + + JetExpression returnedExpression = returnExpression.getReturnedExpression(); + if (returnedExpression == null) { + return KotlinBuiltIns.getInstance().getUnitType(); + } + JetType returnedType = temporaryTrace.get(EXPRESSION_TYPE, returnedExpression); + assert returnedType != null : "No type for returned expression: " + returnedExpression + ",\n" + + "the type should have been computed by getBlockReturnedType() above"; + return returnedType; + } + }); + } + + public static Collection collectReturns(@NotNull JetExpression expression) { + Collection result = Lists.newArrayList(); + expression.accept( + new JetTreeVisitor>() { + @Override + public Void visitReturnExpression( + JetReturnExpression expression, Collection data + ) { + data.add(expression); + return null; + } + }, + result + ); + return result; } } diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/types/expressions/ExpressionTypingServices.java b/compiler/frontend/src/org/jetbrains/jet/lang/types/expressions/ExpressionTypingServices.java index 0fd67f56a8e..6f0dbe648b2 100644 --- a/compiler/frontend/src/org/jetbrains/jet/lang/types/expressions/ExpressionTypingServices.java +++ b/compiler/frontend/src/org/jetbrains/jet/lang/types/expressions/ExpressionTypingServices.java @@ -154,10 +154,10 @@ public class ExpressionTypingServices { } checkFunctionReturnType(function, ExpressionTypingContext.newContext( this, trace, functionInnerScope, dataFlowInfo, expectedReturnType != null ? expectedReturnType : NO_EXPECTED_TYPE, ExpressionPosition.FREE - ), trace); + )); } - /*package*/ void checkFunctionReturnType(JetDeclarationWithBody function, ExpressionTypingContext context, BindingTrace trace) { + /*package*/ void checkFunctionReturnType(JetDeclarationWithBody function, ExpressionTypingContext context) { JetExpression bodyExpression = function.getBodyExpression(); if (bodyExpression == null) return; @@ -167,15 +167,7 @@ public class ExpressionTypingServices { ? context.replaceExpectedType(NO_EXPECTED_TYPE) : context; - if (function instanceof JetFunctionLiteral) { - JetFunctionLiteral functionLiteral = (JetFunctionLiteral) function; - JetBlockExpression blockExpression = functionLiteral.getBodyExpression(); - assert blockExpression != null; - getBlockReturnedType(blockExpression, CoercionStrategy.COERCION_TO_UNIT, context); - } - else { - expressionTypingFacade.getTypeInfo(bodyExpression, newContext, !blockBody); - } + expressionTypingFacade.getTypeInfo(bodyExpression, newContext, !blockBody); } @NotNull diff --git a/compiler/testData/diagnostics/tests/functionLiterals/return/LocalAndNonLocalReturnInLambda.kt b/compiler/testData/diagnostics/tests/functionLiterals/return/LocalAndNonLocalReturnInLambda.kt new file mode 100644 index 00000000000..bce3c7a961a --- /dev/null +++ b/compiler/testData/diagnostics/tests/functionLiterals/return/LocalAndNonLocalReturnInLambda.kt @@ -0,0 +1,8 @@ +fun test2(a: Int) { + (run @f{ + if (a > 0) return + return@f 1 + }): Int +} + +fun run(f: () -> T): T { return f() } \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/functionLiterals/return/LocalReturnExplicitLabelNoParens.kt b/compiler/testData/diagnostics/tests/functionLiterals/return/LocalReturnExplicitLabelNoParens.kt index 9d4d7b89be9..ec6760299be 100644 --- a/compiler/testData/diagnostics/tests/functionLiterals/return/LocalReturnExplicitLabelNoParens.kt +++ b/compiler/testData/diagnostics/tests/functionLiterals/return/LocalReturnExplicitLabelNoParens.kt @@ -1,5 +1,5 @@ fun test2() { - run @f{return@f 1} + (run @f{return@f 1}): Int } fun run(f: () -> T): T { return f() } \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/functionLiterals/return/LocalReturnExplicitLabelParens.kt b/compiler/testData/diagnostics/tests/functionLiterals/return/LocalReturnExplicitLabelParens.kt index 24d4e2d6455..edc92bb1971 100644 --- a/compiler/testData/diagnostics/tests/functionLiterals/return/LocalReturnExplicitLabelParens.kt +++ b/compiler/testData/diagnostics/tests/functionLiterals/return/LocalReturnExplicitLabelParens.kt @@ -1,10 +1,10 @@ fun test() { - run(@f{return@f 1}) + run(@f{return@f 1}): Int } fun test1() { - run(@{return@ 1}) + run(@{return@ 1}): Int } fun run(f: () -> T): T { return f() } \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/functionLiterals/return/LocalReturnInNestedFunction.kt b/compiler/testData/diagnostics/tests/functionLiterals/return/LocalReturnInNestedFunction.kt new file mode 100644 index 00000000000..8a4bba7bb27 --- /dev/null +++ b/compiler/testData/diagnostics/tests/functionLiterals/return/LocalReturnInNestedFunction.kt @@ -0,0 +1,11 @@ +fun test() { + (run @f{ + fun local(a: Int): String { + if (a > 0) return "2" + return@local "3" + } + return@f 1 + }): Int +} + +fun run(f: () -> T): T { return f() } \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/functionLiterals/return/LocalReturnInNestedLambda.kt b/compiler/testData/diagnostics/tests/functionLiterals/return/LocalReturnInNestedLambda.kt new file mode 100644 index 00000000000..2fa1a8975d3 --- /dev/null +++ b/compiler/testData/diagnostics/tests/functionLiterals/return/LocalReturnInNestedLambda.kt @@ -0,0 +1,10 @@ +fun test() { + (run @f{ + run @ff { + return@ff "2" + } + return@f 1 + }): Int +} + +fun run(f: () -> T): T { return f() } \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/functionLiterals/return/LocalReturnNoCoercionToUnit.kt b/compiler/testData/diagnostics/tests/functionLiterals/return/LocalReturnNoCoercionToUnit.kt new file mode 100644 index 00000000000..f7e24127b6a --- /dev/null +++ b/compiler/testData/diagnostics/tests/functionLiterals/return/LocalReturnNoCoercionToUnit.kt @@ -0,0 +1,8 @@ +fun test(a: Int) { + run @f{ + if (a > 0) return@f + else return@f 1 + } +} + +fun run(f: () -> T): T { return f() } \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/functionLiterals/return/LocalReturnUnit.kt b/compiler/testData/diagnostics/tests/functionLiterals/return/LocalReturnUnit.kt new file mode 100644 index 00000000000..ddc0329d4b8 --- /dev/null +++ b/compiler/testData/diagnostics/tests/functionLiterals/return/LocalReturnUnit.kt @@ -0,0 +1,8 @@ +fun test(a: Int) { + (run @f{ + if (a > 0) return@f + else return@f Unit.VALUE + }): Unit +} + +fun run(f: () -> T): T { return f() } \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/functionLiterals/return/MixedReturnsFromLambda.kt b/compiler/testData/diagnostics/tests/functionLiterals/return/MixedReturnsFromLambda.kt new file mode 100644 index 00000000000..397dee4263d --- /dev/null +++ b/compiler/testData/diagnostics/tests/functionLiterals/return/MixedReturnsFromLambda.kt @@ -0,0 +1,13 @@ +trait A +trait B: A +trait C: A + + +fun test(a: C, b: B) { + (run @f{ + if (a != b) return@f a + b + }): A +} + +fun run(f: () -> T): T { return f() } \ 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 d7403c98ece..d455b0d805a 100644 --- a/compiler/tests/org/jetbrains/jet/checkers/JetDiagnosticsTestGenerated.java +++ b/compiler/tests/org/jetbrains/jet/checkers/JetDiagnosticsTestGenerated.java @@ -2437,6 +2437,11 @@ public class JetDiagnosticsTestGenerated extends AbstractDiagnosticsTestWithEage doTest("compiler/testData/diagnostics/tests/functionLiterals/return/ForbiddenNonLocalReturnNoType.kt"); } + @TestMetadata("LocalAndNonLocalReturnInLambda.kt") + public void testLocalAndNonLocalReturnInLambda() throws Exception { + doTest("compiler/testData/diagnostics/tests/functionLiterals/return/LocalAndNonLocalReturnInLambda.kt"); + } + @TestMetadata("LocalReturnExplicitLabelNoParens.kt") public void testLocalReturnExplicitLabelNoParens() throws Exception { doTest("compiler/testData/diagnostics/tests/functionLiterals/return/LocalReturnExplicitLabelNoParens.kt"); @@ -2447,6 +2452,31 @@ public class JetDiagnosticsTestGenerated extends AbstractDiagnosticsTestWithEage doTest("compiler/testData/diagnostics/tests/functionLiterals/return/LocalReturnExplicitLabelParens.kt"); } + @TestMetadata("LocalReturnInNestedFunction.kt") + public void testLocalReturnInNestedFunction() throws Exception { + doTest("compiler/testData/diagnostics/tests/functionLiterals/return/LocalReturnInNestedFunction.kt"); + } + + @TestMetadata("LocalReturnInNestedLambda.kt") + public void testLocalReturnInNestedLambda() throws Exception { + doTest("compiler/testData/diagnostics/tests/functionLiterals/return/LocalReturnInNestedLambda.kt"); + } + + @TestMetadata("LocalReturnNoCoercionToUnit.kt") + public void testLocalReturnNoCoercionToUnit() throws Exception { + doTest("compiler/testData/diagnostics/tests/functionLiterals/return/LocalReturnNoCoercionToUnit.kt"); + } + + @TestMetadata("LocalReturnUnit.kt") + public void testLocalReturnUnit() throws Exception { + doTest("compiler/testData/diagnostics/tests/functionLiterals/return/LocalReturnUnit.kt"); + } + + @TestMetadata("MixedReturnsFromLambda.kt") + public void testMixedReturnsFromLambda() throws Exception { + doTest("compiler/testData/diagnostics/tests/functionLiterals/return/MixedReturnsFromLambda.kt"); + } + } public static Test innerSuite() {