From d6894091a92c740f0044b15e3640d0519b5fa7cd Mon Sep 17 00:00:00 2001 From: Dmitry Petrov Date: Fri, 8 Jun 2018 12:51:33 +0300 Subject: [PATCH] Support 'when' with subject variable in JVM BE --- .../kotlin/codegen/ExpressionCodegen.java | 71 ++++++++++++------- .../captureSubjectVariable.kt | 17 +++++ .../equalityWithSubjectVariable.kt | 10 +++ .../isCheckOnSubjectVariable.kt | 10 +++ .../rangeCheckOnSubjectVariable.kt | 9 +++ .../ir/IrBlackBoxCodegenTestGenerated.java | 33 +++++++++ .../codegen/BlackBoxCodegenTestGenerated.java | 33 +++++++++ .../LightAnalysisModeTestGenerated.java | 33 +++++++++ .../semantics/JsCodegenBoxTestGenerated.java | 33 +++++++++ 9 files changed, 225 insertions(+), 24 deletions(-) create mode 100644 compiler/testData/codegen/box/when/whenSubjectVariable/captureSubjectVariable.kt create mode 100644 compiler/testData/codegen/box/when/whenSubjectVariable/equalityWithSubjectVariable.kt create mode 100644 compiler/testData/codegen/box/when/whenSubjectVariable/isCheckOnSubjectVariable.kt create mode 100644 compiler/testData/codegen/box/when/whenSubjectVariable/rangeCheckOnSubjectVariable.kt diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/ExpressionCodegen.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/ExpressionCodegen.java index 0a52e80b315..a396fe13af7 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/ExpressionCodegen.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/ExpressionCodegen.java @@ -23,7 +23,9 @@ import org.jetbrains.kotlin.builtins.KotlinBuiltIns; import org.jetbrains.kotlin.codegen.binding.CalculatedClosure; import org.jetbrains.kotlin.codegen.binding.CodegenBinding; import org.jetbrains.kotlin.codegen.context.*; -import org.jetbrains.kotlin.codegen.coroutines.*; +import org.jetbrains.kotlin.codegen.coroutines.CoroutineCodegenForLambda; +import org.jetbrains.kotlin.codegen.coroutines.CoroutineCodegenUtilKt; +import org.jetbrains.kotlin.codegen.coroutines.ResolvedCallWithRealDescriptor; import org.jetbrains.kotlin.codegen.extensions.ExpressionCodegenExtension; import org.jetbrains.kotlin.codegen.inline.*; import org.jetbrains.kotlin.codegen.intrinsics.*; @@ -3140,10 +3142,10 @@ public class ExpressionCodegen extends KtVisitor impleme @Nullable KtExpression left, @Nullable KtExpression right, @NotNull IElementType opToken, - @Nullable StackValue pregeneratedLeft, + @Nullable StackValue pregeneratedSubject, @Nullable PrimitiveNumericComparisonInfo primitiveNumericComparisonInfo ) { - Type leftType = expressionType(left); + Type leftType = pregeneratedSubject != null ? pregeneratedSubject.type : expressionType(left); Type rightType = expressionType(right); if (KtPsiUtil.isNullConstant(left)) { @@ -3151,7 +3153,7 @@ public class ExpressionCodegen extends KtVisitor impleme } if (KtPsiUtil.isNullConstant(right)) { - return genCmpWithNull(left, opToken, pregeneratedLeft); + return genCmpWithNull(left, opToken, pregeneratedSubject); } if (isIntZero(left, leftType) && isIntPrimitive(rightType)) { @@ -3159,23 +3161,23 @@ public class ExpressionCodegen extends KtVisitor impleme } if (isIntZero(right, rightType) && isIntPrimitive(leftType)) { - return genCmpWithZero(left, opToken, pregeneratedLeft); + return genCmpWithZero(left, opToken, pregeneratedSubject); } - if (pregeneratedLeft == null && left instanceof KtSafeQualifiedExpression && + if (pregeneratedSubject == null && left instanceof KtSafeQualifiedExpression && isSelectorPureNonNullType((KtSafeQualifiedExpression) left) && isPrimitive(rightType)) { return genCmpSafeCallToPrimitive((KtSafeQualifiedExpression) left, right, rightType, opToken); } if (isPrimitive(leftType) && right instanceof KtSafeQualifiedExpression && isSelectorPureNonNullType(((KtSafeQualifiedExpression) right))) { - return genCmpPrimitiveToSafeCall(left, leftType, (KtSafeQualifiedExpression) right, opToken, pregeneratedLeft); + return genCmpPrimitiveToSafeCall(left, leftType, (KtSafeQualifiedExpression) right, opToken, pregeneratedSubject); } if (BoxedToPrimitiveEquality.isApplicable(opToken, leftType, rightType)) { return BoxedToPrimitiveEquality.create( opToken, - genLazyUnlessProvided(pregeneratedLeft, left, leftType), leftType, + genLazyUnlessProvided(pregeneratedSubject, left, leftType), leftType, genLazy(right, rightType), rightType, myFrameMap ); @@ -3184,7 +3186,7 @@ public class ExpressionCodegen extends KtVisitor impleme if (PrimitiveToBoxedEquality.isApplicable(opToken, leftType, rightType)) { return PrimitiveToBoxedEquality.create( opToken, - genLazyUnlessProvided(pregeneratedLeft, left, leftType), leftType, + genLazyUnlessProvided(pregeneratedSubject, left, leftType), leftType, genLazy(right, rightType), rightType ); } @@ -3192,7 +3194,7 @@ public class ExpressionCodegen extends KtVisitor impleme if (PrimitiveToObjectEquality.isApplicable(opToken, leftType, rightType)) { return PrimitiveToObjectEquality.create( opToken, - genLazyUnlessProvided(pregeneratedLeft, left, leftType), leftType, + genLazyUnlessProvided(pregeneratedSubject, left, leftType), leftType, genLazy(right, rightType), rightType ); } @@ -3209,28 +3211,34 @@ public class ExpressionCodegen extends KtVisitor impleme return StackValue.cmp( opToken, operandType, - genLazyUnlessProvided(pregeneratedLeft, left, leftType), + genLazyUnlessProvided(pregeneratedSubject, left, leftType), genLazy(right, rightType) ); } if ((opToken == KtTokens.EQEQ || opToken == KtTokens.EXCLEQ) && - (isEnumClass(bindingContext.getType(left).getConstructor().getDeclarationDescriptor()) || - isEnumClass(bindingContext.getType(right).getConstructor().getDeclarationDescriptor()))) { + (isEnumExpression(left) || isEnumExpression(right))) { // Reference equality can be used for enums. return StackValue.cmp( opToken == KtTokens.EQEQ ? KtTokens.EQEQEQ : KtTokens.EXCLEQEQEQ, OBJECT_TYPE, - genLazyUnlessProvided(pregeneratedLeft, left, leftType), + genLazyUnlessProvided(pregeneratedSubject, left, leftType), genLazy(right, rightType) ); } return genEqualsForExpressionsPreferIeee754Arithmetic( - left, right, opToken, leftType, rightType, pregeneratedLeft, primitiveNumericComparisonInfo + left, right, opToken, leftType, rightType, pregeneratedSubject, primitiveNumericComparisonInfo ); } + private boolean isEnumExpression(@Nullable KtExpression expression) { + KotlinType expressionType = bindingContext.getType(expression); + if (expressionType == null) return false; + return isEnumClass(expressionType.getConstructor().getDeclarationDescriptor()); + } + + private boolean isSelectorPureNonNullType(@NotNull KtSafeQualifiedExpression safeExpression) { KtExpression expression = safeExpression.getSelectorExpression(); if (expression == null) return false; @@ -4489,12 +4497,12 @@ The "returned" value of try expression with no finally is either the last expres } public StackValue generateWhenExpression(KtWhenExpression expression, boolean isStatement) { - KtExpression expr = expression.getSubjectExpression(); - Type subjectType = expressionType(expr); - Type resultType = isStatement ? Type.VOID_TYPE : expressionType(expression); return StackValue.operation(resultType, v -> { + KtProperty subjectVariable = expression.getSubjectVariable(); + KtExpression subjectExpression = expression.getSubjectExpression(); + SwitchCodegen switchCodegen = switchCodegenProvider.buildAppropriateSwitchCodegenIfPossible( expression, isStatement, CodegenUtil.isExhaustive(bindingContext, expression, isStatement) ); @@ -4503,12 +4511,27 @@ The "returned" value of try expression with no finally is either the last expres return null; } - int subjectLocal = expr != null ? myFrameMap.enterTemp(subjectType) : -1; - if (subjectLocal != -1) { - gen(expr, subjectType); - tempVariables.put(expr, StackValue.local(subjectLocal, subjectType)); + int subjectLocal; + Type subjectType; + if (subjectVariable != null) { + VariableDescriptor variableDescriptor = bindingContext.get(BindingContext.VARIABLE, subjectVariable); + assert variableDescriptor != null : "Unresolved subject variable: " + subjectVariable.getName(); + subjectType = asmType(variableDescriptor.getType()); + subjectLocal = myFrameMap.enter(variableDescriptor, subjectType); + visitProperty(subjectVariable, null); + tempVariables.put(subjectExpression, StackValue.local(subjectLocal, subjectType)); + } + else if (subjectExpression != null) { + subjectType = expressionType(subjectExpression); + subjectLocal = myFrameMap.enterTemp(subjectType); + gen(subjectExpression, subjectType); + tempVariables.put(subjectExpression, StackValue.local(subjectLocal, subjectType)); v.store(subjectLocal, subjectType); } + else { + subjectLocal = -1; + subjectType = Type.VOID_TYPE; + } Label end = new Label(); boolean hasElse = KtPsiUtil.checkWhenExpressionHasSingleElse(expression); @@ -4524,7 +4547,7 @@ The "returned" value of try expression with no finally is either the last expres if (!whenEntry.isElse()) { KtWhenCondition[] conditions = whenEntry.getConditions(); for (int i = 0; i < conditions.length; i++) { - StackValue conditionValue = generateWhenCondition(expr, subjectType, subjectLocal, conditions[i]); + StackValue conditionValue = generateWhenCondition(subjectExpression, subjectType, subjectLocal, conditions[i]); BranchedValue.Companion.condJump(conditionValue, nextCondition, true, v); if (i < conditions.length - 1) { v.goTo(thisEntry); @@ -4550,7 +4573,7 @@ The "returned" value of try expression with no finally is either the last expres v.mark(end); myFrameMap.leaveTemp(subjectType); - tempVariables.remove(expr); + tempVariables.remove(subjectExpression); return null; }); } diff --git a/compiler/testData/codegen/box/when/whenSubjectVariable/captureSubjectVariable.kt b/compiler/testData/codegen/box/when/whenSubjectVariable/captureSubjectVariable.kt new file mode 100644 index 00000000000..8cf748b3574 --- /dev/null +++ b/compiler/testData/codegen/box/when/whenSubjectVariable/captureSubjectVariable.kt @@ -0,0 +1,17 @@ +// !LANGUAGE: +VariableDeclarationInWhenSubject +// IGNORE_BACKEND: JS + +fun box(): String { + var y: String = "OK" + + var materializer: (() -> String)? = null + + when (val x = y) { + "OK" -> materializer = { x } + else -> return "x is $x" + } + + y = "Fail" + + return materializer!!.invoke() +} \ No newline at end of file diff --git a/compiler/testData/codegen/box/when/whenSubjectVariable/equalityWithSubjectVariable.kt b/compiler/testData/codegen/box/when/whenSubjectVariable/equalityWithSubjectVariable.kt new file mode 100644 index 00000000000..ffaea5b65a4 --- /dev/null +++ b/compiler/testData/codegen/box/when/whenSubjectVariable/equalityWithSubjectVariable.kt @@ -0,0 +1,10 @@ +// !LANGUAGE: +VariableDeclarationInWhenSubject +// IGNORE_BACKEND: JS + +val x = 1 + +fun box() = + when (val y = x) { + 1 -> "OK" + else -> "Fail: $y" + } \ No newline at end of file diff --git a/compiler/testData/codegen/box/when/whenSubjectVariable/isCheckOnSubjectVariable.kt b/compiler/testData/codegen/box/when/whenSubjectVariable/isCheckOnSubjectVariable.kt new file mode 100644 index 00000000000..0d25c5f3d56 --- /dev/null +++ b/compiler/testData/codegen/box/when/whenSubjectVariable/isCheckOnSubjectVariable.kt @@ -0,0 +1,10 @@ +// !LANGUAGE: +VariableDeclarationInWhenSubject +// IGNORE_BACKEND: JS + +val x: Any = 1 + +fun box() = + when (val y = x) { + is Int -> "OK" + else -> "Fail: $y" + } \ No newline at end of file diff --git a/compiler/testData/codegen/box/when/whenSubjectVariable/rangeCheckOnSubjectVariable.kt b/compiler/testData/codegen/box/when/whenSubjectVariable/rangeCheckOnSubjectVariable.kt new file mode 100644 index 00000000000..c093688f7d6 --- /dev/null +++ b/compiler/testData/codegen/box/when/whenSubjectVariable/rangeCheckOnSubjectVariable.kt @@ -0,0 +1,9 @@ +// !LANGUAGE: +VariableDeclarationInWhenSubject + +val x = 1 + +fun box() = + when (val y = x) { + in 0..2 -> "OK" + else -> "Fail: $y" + } \ No newline at end of file diff --git a/compiler/tests-ir-jvm/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java b/compiler/tests-ir-jvm/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java index 1f5aa762eb4..8f1d6201ecf 100644 --- a/compiler/tests-ir-jvm/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java +++ b/compiler/tests-ir-jvm/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java @@ -21650,5 +21650,38 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes runTest("compiler/testData/codegen/box/when/stringOptimization/statement.kt"); } } + + @TestMetadata("compiler/testData/codegen/box/when/whenSubjectVariable") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class WhenSubjectVariable extends AbstractIrBlackBoxCodegenTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doTest, TargetBackend.JVM, testDataFilePath); + } + + public void testAllFilesPresentInWhenSubjectVariable() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/when/whenSubjectVariable"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JVM, true); + } + + @TestMetadata("captureSubjectVariable.kt") + public void testCaptureSubjectVariable() throws Exception { + runTest("compiler/testData/codegen/box/when/whenSubjectVariable/captureSubjectVariable.kt"); + } + + @TestMetadata("equalityWithSubjectVariable.kt") + public void testEqualityWithSubjectVariable() throws Exception { + runTest("compiler/testData/codegen/box/when/whenSubjectVariable/equalityWithSubjectVariable.kt"); + } + + @TestMetadata("isCheckOnSubjectVariable.kt") + public void testIsCheckOnSubjectVariable() throws Exception { + runTest("compiler/testData/codegen/box/when/whenSubjectVariable/isCheckOnSubjectVariable.kt"); + } + + @TestMetadata("rangeCheckOnSubjectVariable.kt") + public void testRangeCheckOnSubjectVariable() throws Exception { + runTest("compiler/testData/codegen/box/when/whenSubjectVariable/rangeCheckOnSubjectVariable.kt"); + } + } } } diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java index c34fdfcff85..01ceccda028 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java @@ -21650,5 +21650,38 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest { runTest("compiler/testData/codegen/box/when/stringOptimization/statement.kt"); } } + + @TestMetadata("compiler/testData/codegen/box/when/whenSubjectVariable") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class WhenSubjectVariable extends AbstractBlackBoxCodegenTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doTest, TargetBackend.JVM, testDataFilePath); + } + + public void testAllFilesPresentInWhenSubjectVariable() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/when/whenSubjectVariable"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JVM, true); + } + + @TestMetadata("captureSubjectVariable.kt") + public void testCaptureSubjectVariable() throws Exception { + runTest("compiler/testData/codegen/box/when/whenSubjectVariable/captureSubjectVariable.kt"); + } + + @TestMetadata("equalityWithSubjectVariable.kt") + public void testEqualityWithSubjectVariable() throws Exception { + runTest("compiler/testData/codegen/box/when/whenSubjectVariable/equalityWithSubjectVariable.kt"); + } + + @TestMetadata("isCheckOnSubjectVariable.kt") + public void testIsCheckOnSubjectVariable() throws Exception { + runTest("compiler/testData/codegen/box/when/whenSubjectVariable/isCheckOnSubjectVariable.kt"); + } + + @TestMetadata("rangeCheckOnSubjectVariable.kt") + public void testRangeCheckOnSubjectVariable() throws Exception { + runTest("compiler/testData/codegen/box/when/whenSubjectVariable/rangeCheckOnSubjectVariable.kt"); + } + } } } diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java index 085a3e23119..78208a8ecf8 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java @@ -21650,5 +21650,38 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes runTest("compiler/testData/codegen/box/when/stringOptimization/statement.kt"); } } + + @TestMetadata("compiler/testData/codegen/box/when/whenSubjectVariable") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class WhenSubjectVariable extends AbstractLightAnalysisModeTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doTest, TargetBackend.JVM, testDataFilePath); + } + + public void testAllFilesPresentInWhenSubjectVariable() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/when/whenSubjectVariable"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JVM, true); + } + + @TestMetadata("captureSubjectVariable.kt") + public void testCaptureSubjectVariable() throws Exception { + runTest("compiler/testData/codegen/box/when/whenSubjectVariable/captureSubjectVariable.kt"); + } + + @TestMetadata("equalityWithSubjectVariable.kt") + public void testEqualityWithSubjectVariable() throws Exception { + runTest("compiler/testData/codegen/box/when/whenSubjectVariable/equalityWithSubjectVariable.kt"); + } + + @TestMetadata("isCheckOnSubjectVariable.kt") + public void testIsCheckOnSubjectVariable() throws Exception { + runTest("compiler/testData/codegen/box/when/whenSubjectVariable/isCheckOnSubjectVariable.kt"); + } + + @TestMetadata("rangeCheckOnSubjectVariable.kt") + public void testRangeCheckOnSubjectVariable() throws Exception { + runTest("compiler/testData/codegen/box/when/whenSubjectVariable/rangeCheckOnSubjectVariable.kt"); + } + } } } diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/JsCodegenBoxTestGenerated.java b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/JsCodegenBoxTestGenerated.java index b51681d9efb..4c453c75692 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/JsCodegenBoxTestGenerated.java +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/JsCodegenBoxTestGenerated.java @@ -19543,5 +19543,38 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest { runTest("compiler/testData/codegen/box/when/stringOptimization/statement.kt"); } } + + @TestMetadata("compiler/testData/codegen/box/when/whenSubjectVariable") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class WhenSubjectVariable extends AbstractJsCodegenBoxTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest0(this::doTest, TargetBackend.JS, testDataFilePath); + } + + public void testAllFilesPresentInWhenSubjectVariable() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/when/whenSubjectVariable"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JS, true); + } + + @TestMetadata("captureSubjectVariable.kt") + public void testCaptureSubjectVariable() throws Exception { + runTest("compiler/testData/codegen/box/when/whenSubjectVariable/captureSubjectVariable.kt"); + } + + @TestMetadata("equalityWithSubjectVariable.kt") + public void testEqualityWithSubjectVariable() throws Exception { + runTest("compiler/testData/codegen/box/when/whenSubjectVariable/equalityWithSubjectVariable.kt"); + } + + @TestMetadata("isCheckOnSubjectVariable.kt") + public void testIsCheckOnSubjectVariable() throws Exception { + runTest("compiler/testData/codegen/box/when/whenSubjectVariable/isCheckOnSubjectVariable.kt"); + } + + @TestMetadata("rangeCheckOnSubjectVariable.kt") + public void testRangeCheckOnSubjectVariable() throws Exception { + runTest("compiler/testData/codegen/box/when/whenSubjectVariable/rangeCheckOnSubjectVariable.kt"); + } + } } }