diff --git a/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/PatternMatchingTypingVisitor.kt b/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/PatternMatchingTypingVisitor.kt index 6b854fc8990..d6b675f53c5 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/PatternMatchingTypingVisitor.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/PatternMatchingTypingVisitor.kt @@ -19,6 +19,7 @@ package org.jetbrains.kotlin.types.expressions import org.jetbrains.kotlin.builtins.KotlinBuiltIns import org.jetbrains.kotlin.builtins.KotlinBuiltIns.isBoolean import org.jetbrains.kotlin.cfg.WhenChecker +import org.jetbrains.kotlin.config.LanguageFeature import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.descriptors.ClassKind import org.jetbrains.kotlin.descriptors.Modality @@ -34,6 +35,8 @@ import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowInfo import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowValue import org.jetbrains.kotlin.resolve.calls.util.CallMaker import org.jetbrains.kotlin.resolve.checkers.PrimitiveNumericComparisonCallChecker +import org.jetbrains.kotlin.resolve.scopes.LexicalScope +import org.jetbrains.kotlin.resolve.scopes.LexicalScopeKind import org.jetbrains.kotlin.types.* import org.jetbrains.kotlin.types.TypeUtils.NO_EXPECTED_TYPE import org.jetbrains.kotlin.types.checker.KotlinTypeChecker @@ -95,11 +98,52 @@ class PatternMatchingTypingVisitor internal constructor(facade: ExpressionTyping components.dataFlowAnalyzer.recordExpectedType(trace, expression, contextWithExpectedType.expectedType) val contextBeforeSubject = contextWithExpectedType.replaceExpectedType(NO_EXPECTED_TYPE).replaceContextDependency(INDEPENDENT) - // TODO :change scope according to the bound value in the when header - val subjectExpression = expression.subjectExpression - val subjectTypeInfo = subjectExpression?.let { facade.getTypeInfo(it, contextBeforeSubject) } - val contextAfterSubject = subjectTypeInfo?.let { contextBeforeSubject.replaceDataFlowInfo(it.dataFlowInfo) } ?: contextBeforeSubject + // TODO change scope according to the bound value in the when header + + val subjectExpression = expression.subjectExpression + val subjectVariable = expression.subjectVariable + + val subjectTypeInfo: KotlinTypeInfo? + val scopeWithSubjectVariable: LexicalScope? + + if (subjectVariable != null) { + if (!components.languageVersionSettings.supportsFeature(LanguageFeature.VariableDeclarationInWhenSubject)) { + trace.report(Errors.UNSUPPORTED_FEATURE.on( + subjectVariable, + Pair(LanguageFeature.VariableDeclarationInWhenSubject, components.languageVersionSettings) + )) + } + + scopeWithSubjectVariable = ExpressionTypingUtils.newWritableScopeImpl(contextBeforeSubject, LexicalScopeKind.WHEN, components.overloadChecker) + + val (typeInfo, descriptor) = components.localVariableResolver.process(subjectVariable, contextBeforeSubject, contextBeforeSubject.scope, facade) + // NB typeInfo returned by 'localVariableResolver.process(...)' treats local variable declaration as a statement, + // so 'typeInfo' above it has type 'kotlin.Unit'. + // Propagate declared variable type as a "subject expression" type. + subjectTypeInfo = typeInfo.replaceType(descriptor.type) + scopeWithSubjectVariable.addVariableDescriptor(descriptor) + } + else { + scopeWithSubjectVariable = null + + subjectTypeInfo = subjectExpression?.let { facade.getTypeInfo(it, contextBeforeSubject) } + } + + val contextAfterSubject = run { + var context = contextBeforeSubject + if (subjectTypeInfo != null) { + context = context.replaceDataFlowInfo(subjectTypeInfo.dataFlowInfo) + } + if (scopeWithSubjectVariable != null) { + context = context.replaceScope(scopeWithSubjectVariable) + } + context + } + + val contextWithExpectedTypeAndSubjectVariable = scopeWithSubjectVariable?.let { contextWithExpectedType.replaceScope(it) } ?: + contextWithExpectedType + val subjectType = subjectTypeInfo?.type ?: ErrorUtils.createErrorType("Unknown type") val jumpOutPossibleInSubject: Boolean = subjectTypeInfo?.jumpOutPossible ?: false val subjectDataFlowValue = subjectExpression?.let { @@ -112,7 +156,7 @@ class PatternMatchingTypingVisitor internal constructor(facade: ExpressionTyping checkSmartCastsInSubjectIfRequired(expression, contextBeforeSubject, subjectType, possibleTypesForSubject) val dataFlowInfoForEntries = analyzeConditionsInWhenEntries(expression, contextAfterSubject, subjectDataFlowValue, subjectType) - val whenReturnType = inferTypeForWhenExpression(expression, contextWithExpectedType, contextAfterSubject, dataFlowInfoForEntries) + val whenReturnType = inferTypeForWhenExpression(expression, contextWithExpectedTypeAndSubjectVariable, contextAfterSubject, dataFlowInfoForEntries) val whenResultValue = whenReturnType?.let { facade.components.dataFlowValueFactory.createDataFlowValue(expression, it, contextAfterSubject) } val branchesTypeInfo = diff --git a/compiler/psi/src/org/jetbrains/kotlin/psi/KtWhenExpression.java b/compiler/psi/src/org/jetbrains/kotlin/psi/KtWhenExpression.java index 10c113d7fb8..083c9a606ca 100644 --- a/compiler/psi/src/org/jetbrains/kotlin/psi/KtWhenExpression.java +++ b/compiler/psi/src/org/jetbrains/kotlin/psi/KtWhenExpression.java @@ -35,6 +35,11 @@ public class KtWhenExpression extends KtExpressionImpl { return findChildrenByType(KtNodeTypes.WHEN_ENTRY); } + @Nullable + public KtProperty getSubjectVariable() { + return findChildByClass(KtProperty.class); + } + @Nullable public KtExpression getSubjectExpression() { return findChildByClass(KtExpression.class); diff --git a/compiler/testData/diagnostics/tests/when/withSubjectVariable/invisibleOutsideOfWhen.kt b/compiler/testData/diagnostics/tests/when/withSubjectVariable/invisibleOutsideOfWhen.kt new file mode 100644 index 00000000000..43f33af1a4b --- /dev/null +++ b/compiler/testData/diagnostics/tests/when/withSubjectVariable/invisibleOutsideOfWhen.kt @@ -0,0 +1,14 @@ +// !LANGUAGE: +VariableDeclarationInWhenSubject +// !DIAGNOSTICS: -UNUSED_VARIABLE + +fun foo(): Any = 42 + +fun test(x: Any) { + val z1 = when (val y = foo()) { + 42 -> "Magic: $y, $x" + else -> { + "Not magic: $y, $x" + } + } + val z2 = "Anyway, it was $y" +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/when/withSubjectVariable/invisibleOutsideOfWhen.txt b/compiler/testData/diagnostics/tests/when/withSubjectVariable/invisibleOutsideOfWhen.txt new file mode 100644 index 00000000000..7227d2c6116 --- /dev/null +++ b/compiler/testData/diagnostics/tests/when/withSubjectVariable/invisibleOutsideOfWhen.txt @@ -0,0 +1,4 @@ +package + +public fun foo(): kotlin.Any +public fun test(/*0*/ x: kotlin.Any): kotlin.Unit diff --git a/compiler/testData/diagnostics/tests/when/withSubjectVariable/shadowingOtherVariable.kt b/compiler/testData/diagnostics/tests/when/withSubjectVariable/shadowingOtherVariable.kt new file mode 100644 index 00000000000..6cf9c214e6b --- /dev/null +++ b/compiler/testData/diagnostics/tests/when/withSubjectVariable/shadowingOtherVariable.kt @@ -0,0 +1,11 @@ +// !LANGUAGE: +VariableDeclarationInWhenSubject +// !DIAGNOSTICS: -UNUSED_VARIABLE -UNUSED_PARAMETER + +fun foo(): Any = 42 + +fun test(y: Any) { + val z = when (val y = foo()) { + 42 -> "Magic: $y" + else -> "Not magic: $y" + } +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/when/withSubjectVariable/shadowingOtherVariable.txt b/compiler/testData/diagnostics/tests/when/withSubjectVariable/shadowingOtherVariable.txt new file mode 100644 index 00000000000..eaab2094128 --- /dev/null +++ b/compiler/testData/diagnostics/tests/when/withSubjectVariable/shadowingOtherVariable.txt @@ -0,0 +1,4 @@ +package + +public fun foo(): kotlin.Any +public fun test(/*0*/ y: kotlin.Any): kotlin.Unit diff --git a/compiler/testData/diagnostics/tests/when/withSubjectVariable/unsupportedFeature.kt b/compiler/testData/diagnostics/tests/when/withSubjectVariable/unsupportedFeature.kt new file mode 100644 index 00000000000..bf9a52bfd48 --- /dev/null +++ b/compiler/testData/diagnostics/tests/when/withSubjectVariable/unsupportedFeature.kt @@ -0,0 +1,16 @@ +// !LANGUAGE: -VariableDeclarationInWhenSubject +// !DIAGNOSTICS: -UNUSED_VARIABLE + +fun foo(): Any = 42 + +fun test(x: Any) { + // NB check that we still resolve 'y', even though current language version doesn' support variable declaration in when subject + + val z1 = when (val y = foo()) { + 42 -> "Magic: $y, $x" + else -> { + "Not magic: $y, $x" + } + } + val z2 = "Anyway, it was $y" +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/when/withSubjectVariable/unsupportedFeature.txt b/compiler/testData/diagnostics/tests/when/withSubjectVariable/unsupportedFeature.txt new file mode 100644 index 00000000000..7227d2c6116 --- /dev/null +++ b/compiler/testData/diagnostics/tests/when/withSubjectVariable/unsupportedFeature.txt @@ -0,0 +1,4 @@ +package + +public fun foo(): kotlin.Any +public fun test(/*0*/ x: kotlin.Any): kotlin.Unit diff --git a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java index f89d36c3b98..45bc52ed56b 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java @@ -22457,6 +22457,33 @@ public class DiagnosticsTestGenerated extends AbstractDiagnosticsTest { public void testWhenWithNothingAndLambdas() throws Exception { runTest("compiler/testData/diagnostics/tests/when/whenWithNothingAndLambdas.kt"); } + + @TestMetadata("compiler/testData/diagnostics/tests/when/withSubjectVariable") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class WithSubjectVariable extends AbstractDiagnosticsTest { + public void testAllFilesPresentInWithSubjectVariable() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/diagnostics/tests/when/withSubjectVariable"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.ANY, true); + } + + @TestMetadata("invisibleOutsideOfWhen.kt") + public void testInvisibleOutsideOfWhen() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/when/withSubjectVariable/invisibleOutsideOfWhen.kt"); + doTest(fileName); + } + + @TestMetadata("shadowingOtherVariable.kt") + public void testShadowingOtherVariable() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/when/withSubjectVariable/shadowingOtherVariable.kt"); + doTest(fileName); + } + + @TestMetadata("unsupportedFeature.kt") + public void testUnsupportedFeature() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/when/withSubjectVariable/unsupportedFeature.kt"); + doTest(fileName); + } + } } } diff --git a/compiler/tests/org/jetbrains/kotlin/checkers/javac/DiagnosticsUsingJavacTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/javac/DiagnosticsUsingJavacTestGenerated.java index 1071f75968d..2c58d812520 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/javac/DiagnosticsUsingJavacTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/javac/DiagnosticsUsingJavacTestGenerated.java @@ -22457,6 +22457,33 @@ public class DiagnosticsUsingJavacTestGenerated extends AbstractDiagnosticsUsing public void testWhenWithNothingAndLambdas() throws Exception { runTest("compiler/testData/diagnostics/tests/when/whenWithNothingAndLambdas.kt"); } + + @TestMetadata("compiler/testData/diagnostics/tests/when/withSubjectVariable") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class WithSubjectVariable extends AbstractDiagnosticsUsingJavacTest { + public void testAllFilesPresentInWithSubjectVariable() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/diagnostics/tests/when/withSubjectVariable"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.ANY, true); + } + + @TestMetadata("invisibleOutsideOfWhen.kt") + public void testInvisibleOutsideOfWhen() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/when/withSubjectVariable/invisibleOutsideOfWhen.kt"); + doTest(fileName); + } + + @TestMetadata("shadowingOtherVariable.kt") + public void testShadowingOtherVariable() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/when/withSubjectVariable/shadowingOtherVariable.kt"); + doTest(fileName); + } + + @TestMetadata("unsupportedFeature.kt") + public void testUnsupportedFeature() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/when/withSubjectVariable/unsupportedFeature.kt"); + doTest(fileName); + } + } } } diff --git a/compiler/util/src/org/jetbrains/kotlin/config/LanguageVersionSettings.kt b/compiler/util/src/org/jetbrains/kotlin/config/LanguageVersionSettings.kt index e0b72bcf18b..74d15b50473 100644 --- a/compiler/util/src/org/jetbrains/kotlin/config/LanguageVersionSettings.kt +++ b/compiler/util/src/org/jetbrains/kotlin/config/LanguageVersionSettings.kt @@ -73,6 +73,7 @@ enum class LanguageFeature( ReadDeserializedContracts(KOTLIN_1_3), UseReturnsEffect(KOTLIN_1_3), UseCallsInPlaceEffect(KOTLIN_1_3), + VariableDeclarationInWhenSubject(KOTLIN_1_3), AllowContractsForCustomFunctions(KOTLIN_1_3, kind = UNSTABLE_FEATURE), ProhibitLocalAnnotations(KOTLIN_1_3, kind = BUG_FIX),