Handle nested when with subject properly

Hack: callee expression for when with subject variable is the subject
variable declaration. This solves the problem that all sub-calls in the
expression are implicitly considered to have a single common lexical
scope (and 'when (val x = ...)' introduces a new lexical scope, which
contains 'x').
This commit is contained in:
Dmitry Petrov
2018-06-08 11:25:17 +03:00
parent 758548603e
commit ae929d0f08
7 changed files with 88 additions and 20 deletions
@@ -142,6 +142,13 @@ fun KtElement.getCall(context: BindingContext): Call? {
// Do not use Call bound to outer call expression (if any) to prevent stack overflow during analysis
if (element is KtCallElement && element.calleeExpression == null) return null
if (element is KtWhenExpression) {
val subjectVariable = element.subjectVariable
if (subjectVariable != null) {
return subjectVariable.getCall(context)
}
}
val parent = element.parent
val reference: KtExpression? = when (parent) {
is KtInstanceExpressionWithLabel -> parent
@@ -95,7 +95,7 @@ class PatternMatchingTypingVisitor internal constructor(facade: ExpressionTyping
protected abstract fun createDataFlowValue(contextAfterSubject: ExpressionTypingContext, builtIns: KotlinBuiltIns): DataFlowValue
abstract fun makeValueArgument(): ValueArgument?
abstract val valueExpression: KtExpression?
open fun makeCalleeExpressionForSpecialCall(): KtExpression? = null
open fun getCalleeExpressionForSpecialCall(): KtExpression? = null
lateinit var dataFlowValue: DataFlowValue; private set
@@ -146,8 +146,8 @@ class PatternMatchingTypingVisitor internal constructor(facade: ExpressionTyping
)
}
override fun makeCalleeExpressionForSpecialCall(): KtExpression? =
KtPsiFactory(variable.project, true).createExpression(variable.name!!)
override fun getCalleeExpressionForSpecialCall(): KtExpression? =
variable
override val valueExpression: KtExpression?
get() = variable.initializer
@@ -208,11 +208,6 @@ class PatternMatchingTypingVisitor internal constructor(facade: ExpressionTyping
val contextWithExpectedTypeAndSubjectVariable =
subject.scopeWithSubject?.let { contextWithExpectedType.replaceScope(it) } ?: contextWithExpectedType
// val subjectType = subjectTypeInfo?.type ?: ErrorUtils.createErrorType("Unknown type")
// val jumpOutPossibleInSubject: Boolean = subjectTypeInfo?.jumpOutPossible ?: false
// val subjectDataFlowValue = subjectExpression?.let {
// facade.components.dataFlowValueFactory.createDataFlowValue(it, subjectType, contextAfterSubject)
// } ?: DataFlowValue.nullValue(components.builtIns)
subject.initDataFlowValue(contextAfterSubject, components.builtIns)
val possibleTypesForSubject =
@@ -283,11 +278,8 @@ class PatternMatchingTypingVisitor internal constructor(facade: ExpressionTyping
val scopeWithSubjectVariable =
ExpressionTypingUtils.newWritableScopeImpl(contextBeforeSubject, LexicalScopeKind.WHEN, components.overloadChecker)
// Destructuring causes SOE in UAST :(
val resolveResult =
components.localVariableResolver.process(subjectVariable, contextBeforeSubject, contextBeforeSubject.scope, facade)
val typeInfo = resolveResult.first
val descriptor = resolveResult.second
val (typeInfo, descriptor) =
components.localVariableResolver.process(subjectVariable, contextBeforeSubject, contextBeforeSubject.scope, facade)
scopeWithSubjectVariable.addVariableDescriptor(descriptor)
@@ -313,7 +305,7 @@ class PatternMatchingTypingVisitor internal constructor(facade: ExpressionTyping
val wrappedArgumentExpressions = wrapWhenEntryExpressionsAsSpecialCallArguments(expression)
val callForWhen = createCallForSpecialConstruction(
expression,
subject.makeCalleeExpressionForSpecialCall() ?: expression,
subject.getCalleeExpressionForSpecialCall() ?: expression,
wrappedArgumentExpressions
)
val dataFlowInfoForArguments = createDataFlowInfoForArgumentsOfWhenCall(
@@ -0,0 +1,41 @@
// !LANGUAGE: +VariableDeclarationInWhenSubject
// !DIAGNOSTICS: -UNUSED_VARIABLE -UNUSED_PARAMETER -UNUSED_VALUE
fun foo() {}
fun <T> bar(x: T, y: T) {}
fun test1() {
when (1) {
1 ->
when (val y = 2) {
2 -> foo()
}
}
}
fun test2() {
when (val x = 1) {
1 ->
when (val y = 2) {
2 -> foo()
}
}
}
fun test3() {
when (val x = 1) {
1 ->
when (val <!NAME_SHADOWING!>x<!> = 2) {
2 -> foo()
}
}
}
fun test4() {
when (val x = 1) {
1 ->
when (val y = 2) {
2 -> bar(x, y)
}
}
}
@@ -0,0 +1,8 @@
package
public fun </*0*/ T> bar(/*0*/ x: T, /*1*/ y: T): kotlin.Unit
public fun foo(): kotlin.Unit
public fun test1(): kotlin.Unit
public fun test2(): kotlin.Unit
public fun test3(): kotlin.Unit
public fun test4(): kotlin.Unit
@@ -7,6 +7,7 @@ package org.jetbrains.kotlin.checkers
import com.intellij.openapi.util.io.FileUtil
import com.intellij.openapi.util.text.StringUtil
import com.intellij.psi.PsiElement
import com.intellij.psi.search.GlobalSearchScope
import junit.framework.TestCase
import org.jetbrains.kotlin.analyzer.AnalysisResult
@@ -570,19 +571,28 @@ abstract class AbstractDiagnosticsTest : BaseDiagnosticsTest() {
if (ktFiles.any { file -> AnalyzingUtils.getSyntaxErrorRanges(file).isNotEmpty() }) return
val resolvedCallsEntries = bindingContext.getSliceContents(BindingContext.RESOLVED_CALL)
val unresolvedCallsOnElements = ArrayList<PsiElement>()
for ((call, resolvedCall) in resolvedCallsEntries) {
val element = call.callElement
val lineAndColumn = DiagnosticUtils.getLineAndColumnInPsiFile(element.containingFile, element.textRange)
if (!configuredLanguageVersionSettings.supportsFeature(LanguageFeature.NewInference)) {
assertTrue(
"Resolved call for '${element.text}'$lineAndColumn is not completed",
(resolvedCall as MutableResolvedCall<*>).isCompleted
)
if (!(resolvedCall as MutableResolvedCall<*>).isCompleted) {
unresolvedCallsOnElements.add(element)
}
}
}
if (unresolvedCallsOnElements.isNotEmpty()) {
TestCase.fail(
"There are uncompleted resolved calls for the following elements:\n" +
unresolvedCallsOnElements.joinToString(separator = "\n") { element ->
val lineAndColumn = DiagnosticUtils.getLineAndColumnInPsiFile(element.containingFile, element.textRange)
"'${element.text}'$lineAndColumn"
}
)
}
checkResolvedCallsInDiagnostics(bindingContext, configuredLanguageVersionSettings)
}
@@ -22475,6 +22475,11 @@ public class DiagnosticsTestGenerated extends AbstractDiagnosticsTest {
runTest("compiler/testData/diagnostics/tests/when/withSubjectVariable/invisibleOutsideOfWhen.kt");
}
@TestMetadata("nestedWhenWithSubject.kt")
public void testNestedWhenWithSubject() throws Exception {
runTest("compiler/testData/diagnostics/tests/when/withSubjectVariable/nestedWhenWithSubject.kt");
}
@TestMetadata("reassignmentToWhenSubjectVariable.kt")
public void testReassignmentToWhenSubjectVariable() throws Exception {
runTest("compiler/testData/diagnostics/tests/when/withSubjectVariable/reassignmentToWhenSubjectVariable.kt");
@@ -22475,6 +22475,11 @@ public class DiagnosticsUsingJavacTestGenerated extends AbstractDiagnosticsUsing
runTest("compiler/testData/diagnostics/tests/when/withSubjectVariable/invisibleOutsideOfWhen.kt");
}
@TestMetadata("nestedWhenWithSubject.kt")
public void testNestedWhenWithSubject() throws Exception {
runTest("compiler/testData/diagnostics/tests/when/withSubjectVariable/nestedWhenWithSubject.kt");
}
@TestMetadata("reassignmentToWhenSubjectVariable.kt")
public void testReassignmentToWhenSubjectVariable() throws Exception {
runTest("compiler/testData/diagnostics/tests/when/withSubjectVariable/reassignmentToWhenSubjectVariable.kt");