From ae929d0f084cf258b33de98ae7a9df48866e813c Mon Sep 17 00:00:00 2001 From: Dmitry Petrov Date: Fri, 8 Jun 2018 11:25:17 +0300 Subject: [PATCH] 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'). --- .../kotlin/resolve/calls/util/callUtil.kt | 7 ++++ .../PatternMatchingTypingVisitor.kt | 20 +++------ .../nestedWhenWithSubject.kt | 41 +++++++++++++++++++ .../nestedWhenWithSubject.txt | 8 ++++ .../checkers/AbstractDiagnosticsTest.kt | 22 +++++++--- .../checkers/DiagnosticsTestGenerated.java | 5 +++ .../DiagnosticsUsingJavacTestGenerated.java | 5 +++ 7 files changed, 88 insertions(+), 20 deletions(-) create mode 100644 compiler/testData/diagnostics/tests/when/withSubjectVariable/nestedWhenWithSubject.kt create mode 100644 compiler/testData/diagnostics/tests/when/withSubjectVariable/nestedWhenWithSubject.txt diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/util/callUtil.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/util/callUtil.kt index 4ab3b9a22ee..3cf33c6d90d 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/util/callUtil.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/util/callUtil.kt @@ -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 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 29541bc4e29..414d8be6ce7 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/PatternMatchingTypingVisitor.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/PatternMatchingTypingVisitor.kt @@ -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( diff --git a/compiler/testData/diagnostics/tests/when/withSubjectVariable/nestedWhenWithSubject.kt b/compiler/testData/diagnostics/tests/when/withSubjectVariable/nestedWhenWithSubject.kt new file mode 100644 index 00000000000..8532672f790 --- /dev/null +++ b/compiler/testData/diagnostics/tests/when/withSubjectVariable/nestedWhenWithSubject.kt @@ -0,0 +1,41 @@ +// !LANGUAGE: +VariableDeclarationInWhenSubject +// !DIAGNOSTICS: -UNUSED_VARIABLE -UNUSED_PARAMETER -UNUSED_VALUE + +fun foo() {} +fun 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 x = 2) { + 2 -> foo() + } + } +} + +fun test4() { + when (val x = 1) { + 1 -> + when (val y = 2) { + 2 -> bar(x, y) + } + } +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/when/withSubjectVariable/nestedWhenWithSubject.txt b/compiler/testData/diagnostics/tests/when/withSubjectVariable/nestedWhenWithSubject.txt new file mode 100644 index 00000000000..2cf9afcb80e --- /dev/null +++ b/compiler/testData/diagnostics/tests/when/withSubjectVariable/nestedWhenWithSubject.txt @@ -0,0 +1,8 @@ +package + +public fun 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 diff --git a/compiler/tests-common/tests/org/jetbrains/kotlin/checkers/AbstractDiagnosticsTest.kt b/compiler/tests-common/tests/org/jetbrains/kotlin/checkers/AbstractDiagnosticsTest.kt index f49956ac022..fb1e4678cf8 100644 --- a/compiler/tests-common/tests/org/jetbrains/kotlin/checkers/AbstractDiagnosticsTest.kt +++ b/compiler/tests-common/tests/org/jetbrains/kotlin/checkers/AbstractDiagnosticsTest.kt @@ -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() + 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) } diff --git a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java index 189812db793..1c4925d4de0 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java @@ -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"); diff --git a/compiler/tests/org/jetbrains/kotlin/checkers/javac/DiagnosticsUsingJavacTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/javac/DiagnosticsUsingJavacTestGenerated.java index d02fb444875..b4e877644b8 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/javac/DiagnosticsUsingJavacTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/javac/DiagnosticsUsingJavacTestGenerated.java @@ -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");