Elvis / if / when now infer error type in case of ErrorType + Nothing #KT-6665 Fixed

(cherry picked from commit 9a50a0b)
This commit is contained in:
Mikhail Glukhikh
2016-06-16 17:45:17 +03:00
committed by Mikhail Glukhikh
parent 49fb9ff424
commit 5ccbf47531
8 changed files with 92 additions and 14 deletions
@@ -1175,7 +1175,10 @@ public class BasicExpressionTypingVisitor extends ExpressionTypingVisitor {
DataFlowInfo dataFlowInfo = resolvedCall.getDataFlowInfoForArguments().getInfo(call.getValueArguments().get(1)); DataFlowInfo dataFlowInfo = resolvedCall.getDataFlowInfoForArguments().getInfo(call.getValueArguments().get(1));
KotlinType type = resolvedCall.getResultingDescriptor().getReturnType(); KotlinType type = resolvedCall.getResultingDescriptor().getReturnType();
if (type == null || rightType == null) return TypeInfoFactoryKt.noTypeInfo(dataFlowInfo); if (type == null ||
rightType == null ||
leftType == null && KotlinBuiltIns.isNothing(rightType)) return TypeInfoFactoryKt.noTypeInfo(dataFlowInfo);
if (leftType != null) { if (leftType != null) {
DataFlowValue leftValue = createDataFlowValue(left, leftType, context); DataFlowValue leftValue = createDataFlowValue(left, leftType, context);
DataFlowInfo rightDataFlowInfo = resolvedCall.getDataFlowInfoForArguments().getResultInfo(); DataFlowInfo rightDataFlowInfo = resolvedCall.getDataFlowInfoForArguments().getResultInfo();
@@ -1197,7 +1200,6 @@ public class BasicExpressionTypingVisitor extends ExpressionTypingVisitor {
} }
} }
// Sometimes return type for special call for elvis operator might be nullable, // Sometimes return type for special call for elvis operator might be nullable,
// but result is not nullable if the right type is not nullable // but result is not nullable if the right type is not nullable
if (!TypeUtils.isNullableType(rightType) && TypeUtils.isNullableType(type)) { if (!TypeUtils.isNullableType(rightType) && TypeUtils.isNullableType(type)) {
@@ -179,6 +179,10 @@ public class ControlStructureTypingVisitor extends ExpressionTypingVisitor {
else { else {
resultDataFlowInfo = thenDataFlowInfo.or(elseDataFlowInfo); resultDataFlowInfo = thenDataFlowInfo.or(elseDataFlowInfo);
} }
if (thenType == null && jumpInElse ||
elseType == null && jumpInThen) {
return TypeInfoFactoryKt.noTypeInfo(resultDataFlowInfo);
}
} }
// If break or continue was possible, take condition check info as the jump info // If break or continue was possible, take condition check info as the jump info
return TypeInfoFactoryKt.createTypeInfo( return TypeInfoFactoryKt.createTypeInfo(
@@ -36,6 +36,7 @@ import org.jetbrains.kotlin.types.TypeUtils.NO_EXPECTED_TYPE
import org.jetbrains.kotlin.types.checker.KotlinTypeChecker import org.jetbrains.kotlin.types.checker.KotlinTypeChecker
import org.jetbrains.kotlin.types.expressions.ControlStructureTypingUtils.* import org.jetbrains.kotlin.types.expressions.ControlStructureTypingUtils.*
import org.jetbrains.kotlin.types.expressions.typeInfoFactory.createTypeInfo import org.jetbrains.kotlin.types.expressions.typeInfoFactory.createTypeInfo
import org.jetbrains.kotlin.types.expressions.typeInfoFactory.noTypeInfo
import java.util.* import java.util.*
class PatternMatchingTypingVisitor internal constructor(facade: ExpressionTypingInternals) : ExpressionTypingVisitor(facade) { class PatternMatchingTypingVisitor internal constructor(facade: ExpressionTypingInternals) : ExpressionTypingVisitor(facade) {
@@ -87,29 +88,29 @@ class PatternMatchingTypingVisitor internal constructor(facade: ExpressionTyping
val whenReturnType = inferTypeForWhenExpression(expression, contextWithExpectedType, contextAfterSubject, dataFlowInfoForEntries) val whenReturnType = inferTypeForWhenExpression(expression, contextWithExpectedType, contextAfterSubject, dataFlowInfoForEntries)
val whenResultValue = whenReturnType?.let { DataFlowValueFactory.createDataFlowValue(expression, it, contextAfterSubject) } val whenResultValue = whenReturnType?.let { DataFlowValueFactory.createDataFlowValue(expression, it, contextAfterSubject) }
val (outputDataFlowInfo, jumpOutPossible) = val branchesTypeInfo =
joinWhenExpressionBranches(expression, contextAfterSubject, jumpOutPossibleInSubject, whenResultValue) joinWhenExpressionBranches(expression, contextAfterSubject, whenReturnType, jumpOutPossibleInSubject, whenResultValue)
val isExhaustive = WhenChecker.isWhenExhaustive(expression, trace) val isExhaustive = WhenChecker.isWhenExhaustive(expression, trace)
val branchesDataFlowInfo = branchesTypeInfo.dataFlowInfo
val resultDataFlowInfo = if (expression.elseExpression == null && !isExhaustive) { val resultDataFlowInfo = if (expression.elseExpression == null && !isExhaustive) {
// Without else expression in non-exhaustive when, we *must* take initial data flow info into account, // Without else expression in non-exhaustive when, we *must* take initial data flow info into account,
// because data flow can bypass all when branches in this case // because data flow can bypass all when branches in this case
outputDataFlowInfo.or(contextAfterSubject.dataFlowInfo) branchesDataFlowInfo.or(contextAfterSubject.dataFlowInfo)
} }
else { else {
outputDataFlowInfo branchesDataFlowInfo
} }
if (whenReturnType != null && isExhaustive && expression.elseExpression == null && KotlinBuiltIns.isNothing(whenReturnType)) { if (whenReturnType != null && isExhaustive && expression.elseExpression == null && KotlinBuiltIns.isNothing(whenReturnType)) {
trace.record(BindingContext.IMPLICIT_EXHAUSTIVE_WHEN, expression) trace.record(BindingContext.IMPLICIT_EXHAUSTIVE_WHEN, expression)
} }
val resultType = whenReturnType?.let { val branchesType = branchesTypeInfo.type ?: return noTypeInfo(resultDataFlowInfo)
components.dataFlowAnalyzer.checkType(it, expression, contextWithExpectedType) val resultType = components.dataFlowAnalyzer.checkType(branchesType, expression, contextWithExpectedType)
}
return createTypeInfo(resultType, resultDataFlowInfo, jumpOutPossible, contextWithExpectedType.dataFlowInfo) return createTypeInfo(resultType, resultDataFlowInfo, branchesTypeInfo.jumpOutPossible, contextWithExpectedType.dataFlowInfo)
} }
private fun inferTypeForWhenExpression( private fun inferTypeForWhenExpression(
@@ -172,19 +173,24 @@ class PatternMatchingTypingVisitor internal constructor(facade: ExpressionTyping
private fun joinWhenExpressionBranches( private fun joinWhenExpressionBranches(
expression: KtWhenExpression, expression: KtWhenExpression,
contextAfterSubject: ExpressionTypingContext, contextAfterSubject: ExpressionTypingContext,
resultType: KotlinType?,
jumpOutPossibleInSubject: Boolean, jumpOutPossibleInSubject: Boolean,
whenResultValue: DataFlowValue? whenResultValue: DataFlowValue?
): Pair<DataFlowInfo, Boolean> { ): KotlinTypeInfo {
val bindingContext = contextAfterSubject.trace.bindingContext val bindingContext = contextAfterSubject.trace.bindingContext
var currentDataFlowInfo: DataFlowInfo? = null var currentDataFlowInfo: DataFlowInfo? = null
var jumpOutPossible = jumpOutPossibleInSubject var jumpOutPossible = jumpOutPossibleInSubject
var errorTypeExistInBranch = false
for (whenEntry in expression.entries) { for (whenEntry in expression.entries) {
val entryExpression = whenEntry.expression ?: continue val entryExpression = whenEntry.expression ?: continue
val entryTypeInfo = BindingContextUtils.getRecordedTypeInfo(entryExpression, bindingContext) ?: val entryTypeInfo = BindingContextUtils.getRecordedTypeInfo(entryExpression, bindingContext) ?:
continue continue
val entryType = entryTypeInfo.type val entryType = entryTypeInfo.type
if (entryType == null) {
errorTypeExistInBranch = true
}
val entryDataFlowInfo = val entryDataFlowInfo =
if (whenResultValue != null && entryType != null) { if (whenResultValue != null && entryType != null) {
@@ -206,7 +212,11 @@ class PatternMatchingTypingVisitor internal constructor(facade: ExpressionTyping
jumpOutPossible = jumpOutPossible or entryTypeInfo.jumpOutPossible jumpOutPossible = jumpOutPossible or entryTypeInfo.jumpOutPossible
} }
return Pair(currentDataFlowInfo ?: contextAfterSubject.dataFlowInfo, jumpOutPossible) val resultDataFlowInfo = currentDataFlowInfo ?: contextAfterSubject.dataFlowInfo
return if (resultType == null || errorTypeExistInBranch && KotlinBuiltIns.isNothing(resultType))
noTypeInfo(resultDataFlowInfo)
else
createTypeInfo(resultType, resultDataFlowInfo, jumpOutPossible, resultDataFlowInfo)
} }
private fun checkSmartCastsInSubjectIfRequired( private fun checkSmartCastsInSubjectIfRequired(
@@ -9,7 +9,7 @@ else {
{ true } <!USELESS_ELVIS_ON_LAMBDA_EXPRESSION!>?:<!> null!! { true } <!USELESS_ELVIS_ON_LAMBDA_EXPRESSION!>?:<!> null!!
} }
val <!IMPLICIT_NOTHING_PROPERTY_TYPE!>ww<!> = if (true) { val ww = if (true) {
<!TYPE_MISMATCH!>{ true }<!> <!USELESS_ELVIS_ON_LAMBDA_EXPRESSION!>?:<!> null!! <!TYPE_MISMATCH!>{ true }<!> <!USELESS_ELVIS_ON_LAMBDA_EXPRESSION!>?:<!> null!!
} }
else if (true) { else if (true) {
@@ -7,6 +7,6 @@ public val bbbb: (() -> kotlin.Boolean)?
public val n: kotlin.Nothing public val n: kotlin.Nothing
public val v: () -> kotlin.Boolean public val v: () -> kotlin.Boolean
public val w: () -> kotlin.Boolean public val w: () -> kotlin.Boolean
public val ww: kotlin.Nothing public val ww: ???
public fun f(/*0*/ x: kotlin.Long?): kotlin.Long public fun f(/*0*/ x: kotlin.Long?): kotlin.Long
public fun l(): (() -> kotlin.Boolean)? public fun l(): (() -> kotlin.Boolean)?
@@ -0,0 +1,48 @@
// See KT-6665: unresolved reference (v.bar) should not produce "unreachable code" after it
fun foo(): Int {
val v = 1
val <!UNUSED_VARIABLE!>c<!> = v.<!UNRESOLVED_REFERENCE!>bar<!> ?: return 0
return 42
}
fun foo2(): Int {
val v = 1
val c = if (true) v.<!UNRESOLVED_REFERENCE!>bar<!> else return 3
val <!UNUSED_VARIABLE!>b<!> = <!DEBUG_INFO_ELEMENT_WITH_ERROR_TYPE!>c<!>
return 42
}
fun foo3(): Int {
val v = 1
val c = when {
true -> v.<!UNRESOLVED_REFERENCE!>bar<!>
else -> return 3
}
val <!UNUSED_VARIABLE!>b<!> = <!DEBUG_INFO_ELEMENT_WITH_ERROR_TYPE!>c<!>
return 42
}
// Type + ErrorType should give Type, unless Type is Nothing
fun bar(): Int {
val v = 1
val c = v.<!UNRESOLVED_REFERENCE!>bar<!> ?: 42
return c
}
fun bar2(): Int {
val v = 1
val c = if (true) v.<!UNRESOLVED_REFERENCE!>bar<!> else 3
val b = c
return b
}
fun bar3(): Int {
val v = 1
val c = when {
true -> v.<!UNRESOLVED_REFERENCE!>bar<!>
else -> 3
}
val b = c
return b
}
@@ -0,0 +1,8 @@
package
public fun bar(): kotlin.Int
public fun bar2(): kotlin.Int
public fun bar3(): kotlin.Int
public fun foo(): kotlin.Int
public fun foo2(): kotlin.Int
public fun foo3(): kotlin.Int
@@ -3453,6 +3453,12 @@ public class DiagnosticsTestGenerated extends AbstractDiagnosticsTest {
doTest(fileName); doTest(fileName);
} }
@TestMetadata("unresolvedReference.kt")
public void testUnresolvedReference() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/controlFlowAnalysis/unresolvedReference.kt");
doTest(fileName);
}
@TestMetadata("useUninitializedInLambda.kt") @TestMetadata("useUninitializedInLambda.kt")
public void testUseUninitializedInLambda() throws Exception { public void testUseUninitializedInLambda() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/controlFlowAnalysis/useUninitializedInLambda.kt"); String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/controlFlowAnalysis/useUninitializedInLambda.kt");