Resolve variable declaration in 'when' subject

This commit is contained in:
Dmitry Petrov
2017-10-20 12:21:17 +03:00
parent 84e49e5b57
commit 1eeafc2d0e
11 changed files with 162 additions and 5 deletions
@@ -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 =
@@ -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);
@@ -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 $<!UNRESOLVED_REFERENCE!>y<!>"
}
@@ -0,0 +1,4 @@
package
public fun foo(): kotlin.Any
public fun test(/*0*/ x: kotlin.Any): kotlin.Unit
@@ -0,0 +1,11 @@
// !LANGUAGE: +VariableDeclarationInWhenSubject
// !DIAGNOSTICS: -UNUSED_VARIABLE -UNUSED_PARAMETER
fun foo(): Any = 42
fun test(y: Any) {
val z = when (val <!NAME_SHADOWING!>y<!> = foo()) {
42 -> "Magic: $y"
else -> "Not magic: $y"
}
}
@@ -0,0 +1,4 @@
package
public fun foo(): kotlin.Any
public fun test(/*0*/ y: kotlin.Any): kotlin.Unit
@@ -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 (<!UNSUPPORTED_FEATURE!>val y = foo()<!>) {
42 -> "Magic: $y, $x"
else -> {
"Not magic: $y, $x"
}
}
val z2 = "Anyway, it was $<!UNRESOLVED_REFERENCE!>y<!>"
}
@@ -0,0 +1,4 @@
package
public fun foo(): kotlin.Any
public fun test(/*0*/ x: kotlin.Any): kotlin.Unit
@@ -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);
}
}
}
}
@@ -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);
}
}
}
}
@@ -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),