Introduce separate callee expression for when with subject variable

'Subject.Error' is redundant.
'Subject.None' can be an object.
'Subject#dataFlowValue' can be a lateinit property.

TODO: fix
- parsing local extension properties in 'when' subject
- parsing destructuring declarations in 'when' subject
- non-completed calls in nested 'when' with subject variable
- non-completed calls for subject variable in 'in' pattern
This commit is contained in:
Dmitry Petrov
2017-10-23 13:42:27 +03:00
parent a76bf57694
commit 091b935c2d
10 changed files with 90 additions and 77 deletions
@@ -95,18 +95,19 @@ 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
private var _dataFlowValue: DataFlowValue? = null
val dataFlowValue get() = _dataFlowValue!!
lateinit var dataFlowValue: DataFlowValue; private set
fun initDataFlowValue(contextAfterSubject: ExpressionTypingContext, builtIns: KotlinBuiltIns) {
_dataFlowValue = createDataFlowValue(contextAfterSubject, builtIns)
dataFlowValue = createDataFlowValue(contextAfterSubject, builtIns)
}
val dataFlowInfo get() = typeInfo?.dataFlowInfo
val jumpOutPossible get() = typeInfo?.jumpOutPossible ?: false
class Expression(
val expression: KtExpression,
typeInfo: KotlinTypeInfo
@@ -128,8 +129,6 @@ class PatternMatchingTypingVisitor internal constructor(facade: ExpressionTyping
typeInfo: KotlinTypeInfo,
scopeWithSubject: LexicalScope
) : Subject(variable, typeInfo, scopeWithSubject) {
private val initializer = variable.initializer!!
override fun createDataFlowValue(contextAfterSubject: ExpressionTypingContext, builtIns: KotlinBuiltIns) =
DataFlowValue(
IdentifierInfo.Variable(
@@ -140,18 +139,23 @@ class PatternMatchingTypingVisitor internal constructor(facade: ExpressionTyping
descriptor.type
)
override fun makeValueArgument(): ValueArgument =
CallMaker.makeExternalValueArgument(
KtPsiFactory(variable.project, true).createExpression(variable.name!!),
initializer
)
override fun makeValueArgument(): ValueArgument? =
variable.initializer?.let {
CallMaker.makeExternalValueArgument(
KtPsiFactory(variable.project, true).createExpression(variable.name!!),
it
)
}
override val valueExpression: KtExpression
get() = initializer
override fun makeCalleeExpressionForSpecialCall(): KtExpression? =
KtPsiFactory(variable.project, true).createExpression(variable.name!!)
override val valueExpression: KtExpression?
get() = variable.initializer
}
class None : Subject(null, null, null) {
object None : Subject(null, null, null) {
override fun createDataFlowValue(contextAfterSubject: ExpressionTypingContext, builtIns: KotlinBuiltIns) =
DataFlowValue.nullValue(builtIns)
@@ -160,19 +164,6 @@ class PatternMatchingTypingVisitor internal constructor(facade: ExpressionTyping
override val valueExpression: KtExpression? get() = null
}
class Error(
element: KtElement?,
typeInfo: KotlinTypeInfo?,
scopeWithSubject: LexicalScope?
) : Subject(element, typeInfo, scopeWithSubject) {
override fun createDataFlowValue(contextAfterSubject: ExpressionTypingContext, builtIns: KotlinBuiltIns): DataFlowValue =
DataFlowValue.nullValue(builtIns)
override fun makeValueArgument(): ValueArgument? = null
override val valueExpression: KtExpression? get() = null
}
}
fun visitWhenExpression(
@@ -196,7 +187,7 @@ class PatternMatchingTypingVisitor internal constructor(facade: ExpressionTyping
val subject = when {
subjectVariable != null -> processVariableSubject(subjectVariable, contextBeforeSubject)
subjectExpression != null -> Subject.Expression(subjectExpression, facade.getTypeInfo(subjectExpression, contextBeforeSubject))
else -> Subject.None()
else -> Subject.None
}
val contextAfterSubject = run {
@@ -225,7 +216,7 @@ class PatternMatchingTypingVisitor internal constructor(facade: ExpressionTyping
checkSmartCastsInSubjectIfRequired(expression, contextBeforeSubject, subject.type, possibleTypesForSubject)
val dataFlowInfoForEntries = analyzeConditionsInWhenEntries(expression, contextAfterSubject, subject)
val whenReturnType = inferTypeForWhenExpression(expression, contextWithExpectedTypeAndSubjectVariable, contextAfterSubject, dataFlowInfoForEntries)
val whenReturnType = inferTypeForWhenExpression(expression, subject, contextWithExpectedTypeAndSubjectVariable, contextAfterSubject, dataFlowInfoForEntries)
val whenResultValue = whenReturnType?.let { facade.components.dataFlowValueFactory.createDataFlowValue(expression, it, contextAfterSubject) }
val branchesTypeInfo =
@@ -255,7 +246,6 @@ class PatternMatchingTypingVisitor internal constructor(facade: ExpressionTyping
private fun processVariableSubject(subjectVariable: KtProperty, contextBeforeSubject: ExpressionTypingContext): Subject {
val trace = contextBeforeSubject.trace
var hasIllegalDeclarationInSubject = false
if (!components.languageVersionSettings.supportsFeature(LanguageFeature.VariableDeclarationInWhenSubject)) {
trace.report(UNSUPPORTED_FEATURE.on(
subjectVariable,
@@ -264,6 +254,7 @@ class PatternMatchingTypingVisitor internal constructor(facade: ExpressionTyping
}
else {
val illegalDeclarationString = when {
subjectVariable is KtDestructuringDeclaration -> "destructuring declaration"
subjectVariable.isVar -> "var"
subjectVariable.initializer == null -> "variable without initializer"
subjectVariable.hasDelegateExpression() -> "delegated property"
@@ -272,7 +263,6 @@ class PatternMatchingTypingVisitor internal constructor(facade: ExpressionTyping
}
if (illegalDeclarationString != null) {
hasIllegalDeclarationInSubject = true
trace.report(Errors.ILLEGAL_DECLARATION_IN_WHEN_SUBJECT.on(subjectVariable, illegalDeclarationString))
}
}
@@ -290,36 +280,41 @@ class PatternMatchingTypingVisitor internal constructor(facade: ExpressionTyping
// so 'typeInfo' above it has type 'kotlin.Unit'.
// Propagate declared variable type as a "subject expression" type.
val subjectTypeInfo = typeInfo.replaceType(descriptor.type)
return if (hasIllegalDeclarationInSubject)
Subject.Error(subjectVariable, subjectTypeInfo, scopeWithSubjectVariable)
else
Subject.Variable(subjectVariable, descriptor, subjectTypeInfo, scopeWithSubjectVariable)
return Subject.Variable(subjectVariable, descriptor, subjectTypeInfo, scopeWithSubjectVariable)
}
private fun inferTypeForWhenExpression(
expression: KtWhenExpression,
contextWithExpectedType: ExpressionTypingContext,
contextAfterSubject: ExpressionTypingContext,
dataFlowInfoForEntries: List<DataFlowInfo>
expression: KtWhenExpression,
subject: Subject,
contextWithExpectedType: ExpressionTypingContext,
contextAfterSubject: ExpressionTypingContext,
dataFlowInfoForEntries: List<DataFlowInfo>
): KotlinType? {
if (expression.entries.all { it.expression == null }) {
return components.builtIns.unitType
}
val wrappedArgumentExpressions = wrapWhenEntryExpressionsAsSpecialCallArguments(expression)
val callForWhen = createCallForSpecialConstruction(expression, expression, wrappedArgumentExpressions)
val dataFlowInfoForArguments =
createDataFlowInfoForArgumentsOfWhenCall(callForWhen, contextAfterSubject.dataFlowInfo, dataFlowInfoForEntries)
val callForWhen = createCallForSpecialConstruction(
expression,
subject.makeCalleeExpressionForSpecialCall() ?: expression,
wrappedArgumentExpressions
)
val dataFlowInfoForArguments = createDataFlowInfoForArgumentsOfWhenCall(
callForWhen, contextAfterSubject.dataFlowInfo, dataFlowInfoForEntries
)
val resolvedCall = components.controlStructureTypingUtils.resolveSpecialConstructionAsCall(
callForWhen, ResolveConstruct.WHEN,
callForWhen,
ResolveConstruct.WHEN,
object : AbstractList<String>() {
override fun get(index: Int): String = "entry$index"
override val size: Int get() = wrappedArgumentExpressions.size
},
Collections.nCopies(wrappedArgumentExpressions.size, false),
contextWithExpectedType, dataFlowInfoForArguments
)
contextWithExpectedType,
dataFlowInfoForArguments)
return resolvedCall.resultingDescriptor.returnType
}
@@ -2,10 +2,26 @@
// !DIAGNOSTICS: -UNUSED_VARIABLE -UNUSED_PARAMETER
fun foo(): Any = 42
fun useInt(i: Int) {}
fun test(y: Any) {
val z = when (val <!NAME_SHADOWING!>y<!> = foo()) {
42 -> "Magic: $y"
else -> "Not magic: $y"
fun testShadowingParameter(y: Any) {
when (val <!NAME_SHADOWING!>y<!> = foo()) {
else -> {}
}
}
fun testShadowedInWhenBody(x: Any) {
when (val y = x) {
is String -> {
val <!NAME_SHADOWING!>y<!> = <!DEBUG_INFO_SMARTCAST!>y<!>.length
useInt(y)
}
}
}
fun testShadowinLocalVariable() {
val y = foo()
when (val <!NAME_SHADOWING!>y<!> = foo()) {
else -> {}
}
}
@@ -1,4 +1,7 @@
package
public fun foo(): kotlin.Any
public fun test(/*0*/ y: kotlin.Any): kotlin.Unit
public fun testShadowedInWhenBody(/*0*/ x: kotlin.Any): kotlin.Unit
public fun testShadowinLocalVariable(): kotlin.Unit
public fun testShadowingParameter(/*0*/ y: kotlin.Any): kotlin.Unit
public fun useInt(/*0*/ i: kotlin.Int): kotlin.Unit
@@ -0,0 +1,8 @@
// !LANGUAGE: +VariableDeclarationInWhenSubject
// !DIAGNOSTICS: -UNUSED_VARIABLE -UNUSED_PARAMETER
fun test(x: Any) {
when (val y = x) {
is String -> {}
}
}
@@ -0,0 +1,3 @@
package
public fun test(/*0*/ x: kotlin.Any): kotlin.Unit
@@ -2,8 +2,6 @@
// !DIAGNOSTICS: -UNUSED_VARIABLE -UNUSED_PARAMETER
// WITH_RUNTIME
data class P(val x: Any, val y: Any)
fun foo(): Any = 42
fun String.bar(): Any = 42
@@ -15,26 +13,16 @@ fun testSimpleValInWhenSubject() {
fun testValWithoutInitializerWhenSubject() {
when (<!ILLEGAL_DECLARATION_IN_WHEN_SUBJECT!>val y: Any<!>) {
is String -> <!UNINITIALIZED_VARIABLE!>y<!>.<!UNRESOLVED_REFERENCE!>length<!>
is String -> <!UNINITIALIZED_VARIABLE, DEBUG_INFO_SMARTCAST!>y<!>.length
}
}
fun testVarInWhenSubject() {
when (<!ILLEGAL_DECLARATION_IN_WHEN_SUBJECT!>var y = foo()<!>) {
is String -> y.<!UNRESOLVED_REFERENCE!>length<!>
is String -> <!DEBUG_INFO_SMARTCAST!>y<!>.length
}
}
fun testDestructuringInWhenSubject() {
when (val (y1, y2) = P(1, 2)) {
}
}
fun testExtensionValInWhenSubject() {
when (<!ILLEGAL_DECLARATION_IN_WHEN_SUBJECT!>val <!LOCAL_EXTENSION_PROPERTY, DEBUG_INFO_MISSING_UNRESOLVED!>String<!>.<!VARIABLE_WITH_NO_TYPE_NO_INITIALIZER!>len<!><!><!SYNTAX!><!> <!UNRESOLVED_REFERENCE!>get<!>(<!SYNTAX!><!>) = <!UNRESOLVED_REFERENCE!>bar<!>()<!SYNTAX!>)<!> <!UNUSED_LAMBDA_EXPRESSION!>{
}<!>
}
fun testDelegatedValInWhenSubject() {
when (<!ILLEGAL_DECLARATION_IN_WHEN_SUBJECT!>val y by <!UNRESOLVED_REFERENCE!>lazy<!> { 42 }<!>) {
}
@@ -2,21 +2,7 @@ package
public fun foo(): kotlin.Any
public fun testDelegatedValInWhenSubject(): kotlin.Unit
public fun testDestructuringInWhenSubject(): kotlin.Unit
public fun testExtensionValInWhenSubject(): kotlin.Unit
public fun testSimpleValInWhenSubject(): kotlin.Unit
public fun testValWithoutInitializerWhenSubject(): kotlin.Unit
public fun testVarInWhenSubject(): kotlin.Unit
public fun kotlin.String.bar(): kotlin.Any
public final data class P {
public constructor P(/*0*/ x: kotlin.Any, /*1*/ y: kotlin.Any)
public final val x: kotlin.Any
public final val y: kotlin.Any
public final operator /*synthesized*/ fun component1(): kotlin.Any
public final operator /*synthesized*/ fun component2(): kotlin.Any
public final /*synthesized*/ fun copy(/*0*/ x: kotlin.Any = ..., /*1*/ y: kotlin.Any = ...): P
public open override /*1*/ /*synthesized*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*synthesized*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*synthesized*/ fun toString(): kotlin.String
}
@@ -8,6 +8,7 @@ package org.jetbrains.kotlin.checkers
import com.intellij.openapi.util.io.FileUtil
import com.intellij.openapi.util.text.StringUtil
import com.intellij.psi.search.GlobalSearchScope
import junit.framework.TestCase
import org.jetbrains.kotlin.analyzer.AnalysisResult
import org.jetbrains.kotlin.analyzer.common.CommonAnalyzerFacade
import org.jetbrains.kotlin.cli.jvm.compiler.NoScopeRecordCliBindingTrace
@@ -53,6 +54,7 @@ import org.jetbrains.kotlin.test.util.DescriptorValidator
import org.jetbrains.kotlin.test.util.RecursiveDescriptorComparator
import org.jetbrains.kotlin.test.util.RecursiveDescriptorComparator.RECURSIVE
import org.jetbrains.kotlin.test.util.RecursiveDescriptorComparator.RECURSIVE_ALL
import org.jetbrains.kotlin.utils.addToStdlib.cast
import org.jetbrains.kotlin.utils.keysToMap
import org.junit.Assert
import java.io.File
@@ -22496,6 +22496,12 @@ public class DiagnosticsTestGenerated extends AbstractDiagnosticsTest {
doTest(fileName);
}
@TestMetadata("subjectVariableInIsPattern.kt")
public void testSubjectVariableInIsPattern() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/when/withSubjectVariable/subjectVariableInIsPattern.kt");
doTest(fileName);
}
@TestMetadata("unsupportedFeature.kt")
public void testUnsupportedFeature() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/when/withSubjectVariable/unsupportedFeature.kt");
@@ -22496,6 +22496,12 @@ public class DiagnosticsUsingJavacTestGenerated extends AbstractDiagnosticsUsing
doTest(fileName);
}
@TestMetadata("subjectVariableInIsPattern.kt")
public void testSubjectVariableInIsPattern() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/when/withSubjectVariable/subjectVariableInIsPattern.kt");
doTest(fileName);
}
@TestMetadata("unsupportedFeature.kt")
public void testUnsupportedFeature() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/when/withSubjectVariable/unsupportedFeature.kt");