PatternMatchingTypingVisitor:

rewrite type inference for 'when' using special constructs.
This fixes several type inference issues for 'when':
KT-9929, KT-9972, KT-10439, KT-10463
along with some other diagnostics-related issues.
This commit is contained in:
Dmitry Petrov
2016-01-20 15:28:58 +03:00
parent b49e08fe04
commit f371e67ce8
23 changed files with 280 additions and 152 deletions
@@ -65,7 +65,7 @@ public class ControlStructureTypingUtils {
private static final Logger LOG = Logger.getInstance(ControlStructureTypingUtils.class); private static final Logger LOG = Logger.getInstance(ControlStructureTypingUtils.class);
public enum ResolveConstruct { public enum ResolveConstruct {
IF("if"), ELVIS("elvis"), EXCL_EXCL("ExclExcl"); IF("if"), ELVIS("elvis"), EXCL_EXCL("ExclExcl"), WHEN("when");
private final String name; private final String name;
@@ -188,6 +188,20 @@ public class ControlStructureTypingUtils {
return createIndependentDataFlowInfoForArgumentsForCall(conditionInfo, dataFlowInfoForArgumentsMap); return createIndependentDataFlowInfoForArgumentsForCall(conditionInfo, dataFlowInfoForArgumentsMap);
} }
public static MutableDataFlowInfoForArguments createDataFlowInfoForArgumentsOfWhenCall(
@NotNull Call callForWhen,
@NotNull DataFlowInfo subjectDataFlowInfo,
@NotNull List<DataFlowInfo> entryDataFlowInfos
) {
Map<ValueArgument, DataFlowInfo> dataFlowInfoForArgumentsMap = Maps.newHashMap();
int i = 0;
for (ValueArgument argument : callForWhen.getValueArguments()) {
DataFlowInfo entryDataFlowInfo = entryDataFlowInfos.get(i++);
dataFlowInfoForArgumentsMap.put(argument, entryDataFlowInfo);
}
return createIndependentDataFlowInfoForArgumentsForCall(subjectDataFlowInfo, dataFlowInfoForArgumentsMap);
}
/*package*/ static Call createCallForSpecialConstruction( /*package*/ static Call createCallForSpecialConstruction(
@NotNull final KtExpression expression, @NotNull final KtExpression expression,
@NotNull final KtExpression calleeExpression, @NotNull final KtExpression calleeExpression,
@@ -320,6 +334,19 @@ public class ControlStructureTypingUtils {
return errorWasReported || checkExpressionType(expression, context); return errorWasReported || checkExpressionType(expression, context);
} }
@Override
public Boolean visitWhenExpression(@NotNull KtWhenExpression whenExpression, CheckTypeContext c) {
boolean errorWasReported = false;
for (KtWhenEntry whenEntry : whenExpression.getEntries()) {
KtExpression entryExpression = whenEntry.getExpression();
if (entryExpression != null) {
errorWasReported |= checkExpressionTypeRecursively(entryExpression, c);
}
}
errorWasReported |= checkExpressionType(whenExpression, c);
return errorWasReported;
}
@Override @Override
public Boolean visitIfExpression(@NotNull KtIfExpression ifExpression, CheckTypeContext c) { public Boolean visitIfExpression(@NotNull KtIfExpression ifExpression, CheckTypeContext c) {
KtExpression thenBranch = ifExpression.getThen(); KtExpression thenBranch = ifExpression.getThen();
@@ -32,7 +32,6 @@ import org.jetbrains.kotlin.resolve.calls.context.ResolutionContext;
import org.jetbrains.kotlin.resolve.calls.smartcasts.*; import org.jetbrains.kotlin.resolve.calls.smartcasts.*;
import org.jetbrains.kotlin.resolve.constants.*; import org.jetbrains.kotlin.resolve.constants.*;
import org.jetbrains.kotlin.resolve.constants.evaluate.ConstantExpressionEvaluator; import org.jetbrains.kotlin.resolve.constants.evaluate.ConstantExpressionEvaluator;
import org.jetbrains.kotlin.types.DynamicTypesKt;
import org.jetbrains.kotlin.types.KotlinType; import org.jetbrains.kotlin.types.KotlinType;
import org.jetbrains.kotlin.types.TypeUtils; import org.jetbrains.kotlin.types.TypeUtils;
import org.jetbrains.kotlin.types.checker.KotlinTypeChecker; import org.jetbrains.kotlin.types.checker.KotlinTypeChecker;
@@ -16,29 +16,26 @@
package org.jetbrains.kotlin.types.expressions package org.jetbrains.kotlin.types.expressions
import com.google.common.collect.Maps
import org.jetbrains.kotlin.builtins.KotlinBuiltIns import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.builtins.KotlinBuiltIns.isBoolean import org.jetbrains.kotlin.builtins.KotlinBuiltIns.isBoolean
import org.jetbrains.kotlin.cfg.WhenChecker import org.jetbrains.kotlin.cfg.WhenChecker
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.diagnostics.Errors import org.jetbrains.kotlin.diagnostics.Errors
import org.jetbrains.kotlin.diagnostics.Errors.* import org.jetbrains.kotlin.diagnostics.Errors.*
import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.*
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.TemporaryBindingTrace
import org.jetbrains.kotlin.resolve.TypeResolutionContext
import org.jetbrains.kotlin.resolve.calls.context.ContextDependency.INDEPENDENT import org.jetbrains.kotlin.resolve.calls.context.ContextDependency.INDEPENDENT
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowInfo import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowInfo
import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowValue import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowValue
import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowValueFactory import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowValueFactory
import org.jetbrains.kotlin.resolve.calls.util.CallMaker import org.jetbrains.kotlin.resolve.calls.util.CallMaker
import org.jetbrains.kotlin.resolve.scopes.LexicalScopeKind
import org.jetbrains.kotlin.types.* import org.jetbrains.kotlin.types.*
import org.jetbrains.kotlin.types.TypeUtils.NO_EXPECTED_TYPE 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.ExpressionTypingUtils.newWritableScopeImpl 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.utils.addIfNotNull import java.util.*
class PatternMatchingTypingVisitor internal constructor(facade: ExpressionTypingInternals) : ExpressionTypingVisitor(facade) { class PatternMatchingTypingVisitor internal constructor(facade: ExpressionTypingInternals) : ExpressionTypingVisitor(facade) {
@@ -47,9 +44,10 @@ class PatternMatchingTypingVisitor internal constructor(facade: ExpressionTyping
val leftHandSide = expression.leftHandSide val leftHandSide = expression.leftHandSide
val typeInfo = facade.safeGetTypeInfo(leftHandSide, context.replaceScope(context.scope)) val typeInfo = facade.safeGetTypeInfo(leftHandSide, context.replaceScope(context.scope))
val knownType = typeInfo.type val knownType = typeInfo.type
if (expression.typeReference != null && knownType != null) { val typeReference = expression.typeReference
if (typeReference != null && knownType != null) {
val dataFlowValue = DataFlowValueFactory.createDataFlowValue(leftHandSide, knownType, context) val dataFlowValue = DataFlowValueFactory.createDataFlowValue(leftHandSide, knownType, context)
val conditionInfo = checkTypeForIs(context, knownType, expression.typeReference, dataFlowValue).thenInfo val conditionInfo = checkTypeForIs(context, knownType, typeReference, dataFlowValue).thenInfo
val newDataFlowInfo = conditionInfo.and(typeInfo.dataFlowInfo) val newDataFlowInfo = conditionInfo.and(typeInfo.dataFlowInfo)
context.trace.record(BindingContext.DATAFLOW_INFO_AFTER_CONDITION, expression, newDataFlowInfo) context.trace.record(BindingContext.DATAFLOW_INFO_AFTER_CONDITION, expression, newDataFlowInfo)
} }
@@ -59,7 +57,11 @@ class PatternMatchingTypingVisitor internal constructor(facade: ExpressionTyping
override fun visitWhenExpression(expression: KtWhenExpression, context: ExpressionTypingContext) = override fun visitWhenExpression(expression: KtWhenExpression, context: ExpressionTypingContext) =
visitWhenExpression(expression, context, false) visitWhenExpression(expression, context, false)
fun visitWhenExpression(expression: KtWhenExpression, contextWithExpectedType: ExpressionTypingContext, isStatement: Boolean): KotlinTypeInfo { fun visitWhenExpression(
expression: KtWhenExpression,
contextWithExpectedType: ExpressionTypingContext,
@Suppress("UNUSED_PARAMETER") isStatement: Boolean
): KotlinTypeInfo {
WhenChecker.checkDeprecatedWhenSyntax(contextWithExpectedType.trace, expression) WhenChecker.checkDeprecatedWhenSyntax(contextWithExpectedType.trace, expression)
WhenChecker.checkReservedPrefix(contextWithExpectedType.trace, expression) WhenChecker.checkReservedPrefix(contextWithExpectedType.trace, expression)
@@ -69,127 +71,154 @@ class PatternMatchingTypingVisitor internal constructor(facade: ExpressionTyping
// TODO :change scope according to the bound value in the when header // TODO :change scope according to the bound value in the when header
val subjectExpression = expression.subjectExpression val subjectExpression = expression.subjectExpression
val contextAfterSubject: ExpressionTypingContext val subjectTypeInfo = subjectExpression?.let { facade.getTypeInfo(it, contextBeforeSubject) }
val subjectType: KotlinType val contextAfterSubject = subjectTypeInfo?.let { contextBeforeSubject.replaceDataFlowInfo(it.dataFlowInfo) } ?: contextBeforeSubject
val subjectDataFlowValue: DataFlowValue val subjectType = subjectTypeInfo?.type ?: ErrorUtils.createErrorType("Unknown type")
val jumpOutPossible: Boolean val jumpOutPossibleInSubject: Boolean = subjectTypeInfo?.jumpOutPossible ?: false
val subjectDataFlowValue = subjectExpression?.let {
DataFlowValueFactory.createDataFlowValue(it, subjectType, contextAfterSubject)
} ?: DataFlowValue.nullValue(components.builtIns)
if (subjectExpression == null) { checkSmartCastsInSubjectIfRequired(expression, contextBeforeSubject, subjectType)
subjectType = ErrorUtils.createErrorType("Unknown type")
subjectDataFlowValue = DataFlowValue.nullValue(components.builtIns)
contextAfterSubject = contextBeforeSubject
jumpOutPossible = false
}
else {
val subjectTypeInfo = facade.safeGetTypeInfo(subjectExpression, contextBeforeSubject)
contextAfterSubject = contextBeforeSubject.replaceDataFlowInfo(subjectTypeInfo.dataFlowInfo) val resolvedCall = resolveSpecialCallForWhen(expression, contextWithExpectedType, contextAfterSubject, subjectDataFlowValue, subjectType)
subjectType = subjectTypeInfo.type!!
subjectDataFlowValue = DataFlowValueFactory.createDataFlowValue(subjectExpression, subjectType, contextAfterSubject)
jumpOutPossible = subjectTypeInfo.jumpOutPossible
if (TypeUtils.isNullableType(subjectType) && !WhenChecker.containsNullCase(expression, contextBeforeSubject.trace.bindingContext)) { val whenReturnType = resolvedCall.resultingDescriptor.returnType
val trace = TemporaryBindingTrace.create(contextBeforeSubject.trace, "Temporary trace for when subject nullability") val whenResultValue = whenReturnType?.let { DataFlowValueFactory.createDataFlowValue(expression, it, contextAfterSubject) }
val subjectContext = contextBeforeSubject.replaceExpectedType(TypeUtils.makeNotNullable(subjectType)).replaceBindingTrace(trace)
val castResult = DataFlowAnalyzer.checkPossibleCast( val (outputDataFlowInfo, jumpOutPossible) =
subjectType, KtPsiUtil.safeDeparenthesize(subjectExpression), subjectContext) joinWhenExpressionBranches(expression, contextAfterSubject, jumpOutPossibleInSubject, whenResultValue)
if (castResult != null && castResult.isCorrect) {
trace.commit()
}
}
}
val dataFlowInfoBeforeEntryBodies = collectDataFlowInfoBeforeEntryBodies(expression, contextAfterSubject, subjectType, subjectDataFlowValue)
val isExhaustive = WhenChecker.isWhenExhaustive(expression, contextAfterSubject.trace) val isExhaustive = WhenChecker.isWhenExhaustive(expression, contextAfterSubject.trace)
return getTypeInfoForIncompleteWhen(expression, isStatement, isExhaustive, contextWithExpectedType, val resultDataFlowInfo = if (expression.elseExpression == null && !isExhaustive) {
contextAfterSubject, jumpOutPossible, dataFlowInfoBeforeEntryBodies) // 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
outputDataFlowInfo.or(contextAfterSubject.dataFlowInfo)
}
else {
outputDataFlowInfo
}
if (whenReturnType != null && isExhaustive && expression.elseExpression == null && KotlinBuiltIns.isNothing(whenReturnType)) {
contextAfterSubject.trace.record(BindingContext.IMPLICIT_EXHAUSTIVE_WHEN, expression)
}
val resultType: KotlinType? = whenReturnType?.let {
components.dataFlowAnalyzer.checkType(it, expression, contextWithExpectedType)
}
return createTypeInfo(resultType, resultDataFlowInfo, jumpOutPossible, contextWithExpectedType.dataFlowInfo)
} }
private fun collectDataFlowInfoBeforeEntryBodies( private fun resolveSpecialCallForWhen(
expression: KtWhenExpression,
contextWithExpectedType: ExpressionTypingContext,
contextAfterSubject: ExpressionTypingContext,
subjectDataFlowValue: DataFlowValue,
subjectType: KotlinType
): ResolvedCall<FunctionDescriptor> {
val wrappedArgumentExpressions = wrapWhenEntryExpressionsAsSpecialCallArguments(expression)
val argumentDataFlowInfos = collectInputDataFlowForWhenEntryExpressions(expression, contextAfterSubject, subjectDataFlowValue, subjectType)
val callForWhen = createCallForSpecialConstruction(expression, expression, wrappedArgumentExpressions)
val dataFlowInfoForArguments = createDataFlowInfoForArgumentsOfWhenCall(callForWhen, contextAfterSubject.dataFlowInfo, argumentDataFlowInfos)
return components.controlStructureTypingUtils.resolveSpecialConstructionAsCall(
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)
}
private fun wrapWhenEntryExpressionsAsSpecialCallArguments(expression: KtWhenExpression): List<KtExpression> {
val psiFactory = KtPsiFactory(expression)
val wrappedArgumentExpressions = expression.entries.mapNotNull { whenEntry ->
whenEntry.expression?.let { psiFactory.wrapInABlockWrapper(it) }
}
return wrappedArgumentExpressions
}
private fun collectInputDataFlowForWhenEntryExpressions(
expression: KtWhenExpression, expression: KtWhenExpression,
contextAfterSubject: ExpressionTypingContext, contextAfterSubject: ExpressionTypingContext,
subjectType: KotlinType, subjectDataFlowValue: DataFlowValue,
subjectDataFlowValue: DataFlowValue subjectType: KotlinType
): Map<KtWhenEntry, DataFlowInfo> { ): ArrayList<DataFlowInfo> {
val subjectExpression = expression.subjectExpression val subjectExpression = expression.subjectExpression
val thenInfo = Maps.newHashMapWithExpectedSize<KtWhenEntry, DataFlowInfo>(expression.entries.size)
val argumentDataFlowInfos = ArrayList<DataFlowInfo>()
var inputDataFlowInfo = contextAfterSubject.dataFlowInfo var inputDataFlowInfo = contextAfterSubject.dataFlowInfo
for (whenEntry in expression.entries) { for (whenEntry in expression.entries) {
val conditionsInfo = analyzeWhenEntryConditions(whenEntry, val conditionsInfo = analyzeWhenEntryConditions(whenEntry,
contextAfterSubject.replaceDataFlowInfo(inputDataFlowInfo), contextAfterSubject.replaceDataFlowInfo(inputDataFlowInfo),
subjectExpression, subjectType, subjectDataFlowValue) subjectExpression, subjectType, subjectDataFlowValue)
thenInfo[whenEntry] = conditionsInfo.thenInfo
inputDataFlowInfo = inputDataFlowInfo.and(conditionsInfo.elseInfo) inputDataFlowInfo = inputDataFlowInfo.and(conditionsInfo.elseInfo)
}
return thenInfo if (whenEntry.expression != null) {
argumentDataFlowInfos.add(conditionsInfo.thenInfo)
}
}
return argumentDataFlowInfos
} }
private fun getTypeInfoForIncompleteWhen( private fun joinWhenExpressionBranches(
expression: KtWhenExpression, expression: KtWhenExpression,
isStatement: Boolean,
isExhaustive: Boolean,
contextWithExpectedType: ExpressionTypingContext,
contextAfterSubject: ExpressionTypingContext, contextAfterSubject: ExpressionTypingContext,
jumpOutPossibleInSubject: Boolean, jumpOutPossibleInSubject: Boolean,
dataFlowBeforeEntryBody: Map<KtWhenEntry, DataFlowInfo> whenResultValue: DataFlowValue?
): KotlinTypeInfo { ): Pair<DataFlowInfo, Boolean> {
val coercionStrategy = if (isStatement) CoercionStrategy.COERCION_TO_UNIT else CoercionStrategy.NO_COERCION val bindingContext = contextAfterSubject.trace.bindingContext
val expressionTypes = hashSetOf<KotlinType>() var currentDataFlowInfo: DataFlowInfo? = null
var commonDataFlowInfo: DataFlowInfo? = null
var jumpOutPossible = jumpOutPossibleInSubject var jumpOutPossible = jumpOutPossibleInSubject
val whenValue = DataFlowValueFactory.createDataFlowValue(expression, components.builtIns.nullableAnyType, contextAfterSubject)
for (whenEntry in expression.entries) { for (whenEntry in expression.entries) {
val ifTrueInfo = dataFlowBeforeEntryBody[whenEntry]!! val entryExpression = whenEntry.expression ?: continue
val bodyExpression = whenEntry.expression val entryTypeInfo = BindingContextUtils.getRecordedTypeInfo(entryExpression, bindingContext) ?:
if (bodyExpression != null) { throw AssertionError("When entry was not processed")
val scopeToExtend = newWritableScopeImpl(contextAfterSubject, LexicalScopeKind.WHEN) val entryType = entryTypeInfo.type
val contextForEntry = contextWithExpectedType.replaceScope(scopeToExtend).replaceDataFlowInfo(ifTrueInfo).replaceContextDependency(INDEPENDENT)
val entryTypeInfo = components.expressionTypingServices.getBlockReturnedTypeWithWritableScope(
scopeToExtend, listOf(bodyExpression), coercionStrategy, contextForEntry)
jumpOutPossible = jumpOutPossible or entryTypeInfo.jumpOutPossible val entryDataFlowInfo =
if (whenResultValue != null && entryType != null) {
val entryValue = DataFlowValueFactory.createDataFlowValue(entryExpression, entryType, contextAfterSubject)
entryTypeInfo.dataFlowInfo.assign(whenResultValue, entryValue)
}
else {
entryTypeInfo.dataFlowInfo
}
val entryType = entryTypeInfo.type currentDataFlowInfo =
if (entryType != null && KotlinBuiltIns.isNothing(entryType))
currentDataFlowInfo
else if (currentDataFlowInfo != null)
currentDataFlowInfo.or(entryDataFlowInfo)
else
entryDataFlowInfo
expressionTypes.addIfNotNull(entryType) jumpOutPossible = jumpOutPossible or entryTypeInfo.jumpOutPossible
}
val entryDataFlowInfo = if (entryType != null) { return Pair(currentDataFlowInfo ?: contextAfterSubject.dataFlowInfo, jumpOutPossible)
val entryValue = DataFlowValueFactory.createDataFlowValue(bodyExpression, entryType, contextAfterSubject) }
entryTypeInfo.dataFlowInfo.assign(whenValue, entryValue)
}
else entryTypeInfo.dataFlowInfo
commonDataFlowInfo = commonDataFlowInfo?.or(entryDataFlowInfo) ?: entryDataFlowInfo private fun checkSmartCastsInSubjectIfRequired(expression: KtWhenExpression, contextBeforeSubject: ExpressionTypingContext, subjectType: KotlinType) {
val subjectExpression = expression.subjectExpression
if (subjectExpression != null &&
TypeUtils.isNullableType(subjectType) &&
!WhenChecker.containsNullCase(expression, contextBeforeSubject.trace.bindingContext)
) {
val trace = TemporaryBindingTrace.create(contextBeforeSubject.trace, "Temporary trace for when subject nullability")
val subjectContext = contextBeforeSubject.replaceExpectedType(TypeUtils.makeNotNullable(subjectType)).replaceBindingTrace(trace)
val castResult = DataFlowAnalyzer.checkPossibleCast(
subjectType, KtPsiUtil.safeDeparenthesize(subjectExpression), subjectContext)
if (castResult != null && castResult.isCorrect) {
trace.commit()
} }
} }
if (commonDataFlowInfo == null) {
commonDataFlowInfo = contextAfterSubject.dataFlowInfo
}
else if (expression.elseExpression == null && !isExhaustive) {
// 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
commonDataFlowInfo = commonDataFlowInfo.or(contextAfterSubject.dataFlowInfo)
}
var resultType: KotlinType? = if (expressionTypes.isNotEmpty()) {
val commonSupertype = CommonSupertypes.commonSupertype(expressionTypes)
val resultValue = DataFlowValueFactory.createDataFlowValue(expression, commonSupertype, contextAfterSubject)
commonDataFlowInfo = commonDataFlowInfo.assign(resultValue, whenValue)
if (isExhaustive && expression.elseExpression == null && KotlinBuiltIns.isNothing(commonSupertype)) {
contextAfterSubject.trace.record(BindingContext.IMPLICIT_EXHAUSTIVE_WHEN, expression)
}
components.dataFlowAnalyzer.checkType(commonSupertype, expression, contextWithExpectedType)
}
else null
return createTypeInfo(resultType, commonDataFlowInfo, jumpOutPossible, contextWithExpectedType.dataFlowInfo)
} }
private fun analyzeWhenEntryConditions( private fun analyzeWhenEntryConditions(
@@ -232,14 +261,14 @@ class PatternMatchingTypingVisitor internal constructor(facade: ExpressionTyping
if (subjectExpression == null) { if (subjectExpression == null) {
context.trace.report(EXPECTED_CONDITION.on(condition)) context.trace.report(EXPECTED_CONDITION.on(condition))
val dataFlowInfo = facade.getTypeInfo(rangeExpression, context).dataFlowInfo val dataFlowInfo = facade.getTypeInfo(rangeExpression, context).dataFlowInfo
newDataFlowInfo = ConditionalDataFlowInfo(dataFlowInfo, dataFlowInfo) newDataFlowInfo = ConditionalDataFlowInfo(dataFlowInfo)
return return
} }
val argumentForSubject = CallMaker.makeExternalValueArgument(subjectExpression) val argumentForSubject = CallMaker.makeExternalValueArgument(subjectExpression)
val typeInfo = facade.checkInExpression(condition, condition.operationReference, val typeInfo = facade.checkInExpression(condition, condition.operationReference,
argumentForSubject, rangeExpression, context) argumentForSubject, rangeExpression, context)
val dataFlowInfo = typeInfo.dataFlowInfo val dataFlowInfo = typeInfo.dataFlowInfo
newDataFlowInfo = ConditionalDataFlowInfo(dataFlowInfo, dataFlowInfo) newDataFlowInfo = ConditionalDataFlowInfo(dataFlowInfo)
val type = typeInfo.type val type = typeInfo.type
if (type == null || !isBoolean(type)) { if (type == null || !isBoolean(type)) {
context.trace.report(TYPE_MISMATCH_IN_RANGE.on(condition)) context.trace.report(TYPE_MISMATCH_IN_RANGE.on(condition))
@@ -250,8 +279,9 @@ class PatternMatchingTypingVisitor internal constructor(facade: ExpressionTyping
if (subjectExpression == null) { if (subjectExpression == null) {
context.trace.report(EXPECTED_CONDITION.on(condition)) context.trace.report(EXPECTED_CONDITION.on(condition))
} }
if (condition.typeReference != null) { val typeReference = condition.typeReference
val result = checkTypeForIs(context, subjectType, condition.typeReference, subjectDataFlowValue) if (typeReference != null) {
val result = checkTypeForIs(context, subjectType, typeReference, subjectDataFlowValue)
if (condition.isNegated) { if (condition.isNegated) {
newDataFlowInfo = ConditionalDataFlowInfo(result.elseInfo, result.thenInfo) newDataFlowInfo = ConditionalDataFlowInfo(result.elseInfo, result.thenInfo)
} }
@@ -264,8 +294,8 @@ class PatternMatchingTypingVisitor internal constructor(facade: ExpressionTyping
override fun visitWhenConditionWithExpression(condition: KtWhenConditionWithExpression) { override fun visitWhenConditionWithExpression(condition: KtWhenConditionWithExpression) {
val expression = condition.expression val expression = condition.expression
if (expression != null) { if (expression != null) {
newDataFlowInfo = checkTypeForExpressionCondition(context, expression, subjectType, subjectExpression == null, newDataFlowInfo = checkTypeForExpressionCondition(
subjectDataFlowValue) context, expression, subjectType, subjectExpression == null, subjectDataFlowValue)
} }
} }
@@ -280,15 +310,12 @@ class PatternMatchingTypingVisitor internal constructor(facade: ExpressionTyping
private fun checkTypeForExpressionCondition( private fun checkTypeForExpressionCondition(
context: ExpressionTypingContext, context: ExpressionTypingContext,
expression: KtExpression?, expression: KtExpression,
subjectType: KotlinType, subjectType: KotlinType,
conditionExpected: Boolean, conditionExpected: Boolean,
subjectDataFlowValue: DataFlowValue subjectDataFlowValue: DataFlowValue
): ConditionalDataFlowInfo { ): ConditionalDataFlowInfo {
var newContext = context var newContext = context
if (expression == null) {
return noChange(newContext)
}
val typeInfo = facade.getTypeInfo(expression, newContext) val typeInfo = facade.getTypeInfo(expression, newContext)
val type = typeInfo.type ?: return noChange(newContext) val type = typeInfo.type ?: return noChange(newContext)
newContext = newContext.replaceDataFlowInfo(typeInfo.dataFlowInfo) newContext = newContext.replaceDataFlowInfo(typeInfo.dataFlowInfo)
@@ -306,22 +333,18 @@ class PatternMatchingTypingVisitor internal constructor(facade: ExpressionTyping
} }
checkTypeCompatibility(newContext, type, subjectType, expression) checkTypeCompatibility(newContext, type, subjectType, expression)
val expressionDataFlowValue = DataFlowValueFactory.createDataFlowValue(expression, type, newContext) val expressionDataFlowValue = DataFlowValueFactory.createDataFlowValue(expression, type, newContext)
var result = noChange(newContext) val result = noChange(newContext)
result = ConditionalDataFlowInfo( return ConditionalDataFlowInfo(
result.thenInfo.equate(subjectDataFlowValue, expressionDataFlowValue), result.thenInfo.equate(subjectDataFlowValue, expressionDataFlowValue),
result.elseInfo.disequate(subjectDataFlowValue, expressionDataFlowValue)) result.elseInfo.disequate(subjectDataFlowValue, expressionDataFlowValue))
return result
} }
private fun checkTypeForIs( private fun checkTypeForIs(
context: ExpressionTypingContext, context: ExpressionTypingContext,
subjectType: KotlinType, subjectType: KotlinType,
typeReferenceAfterIs: KtTypeReference?, typeReferenceAfterIs: KtTypeReference,
subjectDataFlowValue: DataFlowValue subjectDataFlowValue: DataFlowValue
): ConditionalDataFlowInfo { ): ConditionalDataFlowInfo {
if (typeReferenceAfterIs == null) {
return noChange(context)
}
val typeResolutionContext = TypeResolutionContext(context.scope, context.trace, true, /*allowBareTypes=*/ true) val typeResolutionContext = TypeResolutionContext(context.scope, context.trace, true, /*allowBareTypes=*/ true)
val possiblyBareTarget = components.typeResolver.resolvePossiblyBareType(typeResolutionContext, typeReferenceAfterIs) val possiblyBareTarget = components.typeResolver.resolvePossiblyBareType(typeResolutionContext, typeReferenceAfterIs)
val targetType = TypeReconstructionUtil.reconstructBareType(typeReferenceAfterIs, possiblyBareTarget, subjectType, context.trace, components.builtIns) val targetType = TypeReconstructionUtil.reconstructBareType(typeReferenceAfterIs, possiblyBareTarget, subjectType, context.trace, components.builtIns)
@@ -346,21 +369,18 @@ class PatternMatchingTypingVisitor internal constructor(facade: ExpressionTyping
return ConditionalDataFlowInfo(context.dataFlowInfo.establishSubtyping(subjectDataFlowValue, targetType), context.dataFlowInfo) return ConditionalDataFlowInfo(context.dataFlowInfo.establishSubtyping(subjectDataFlowValue, targetType), context.dataFlowInfo)
} }
private fun noChange(context: ExpressionTypingContext) = ConditionalDataFlowInfo(context.dataFlowInfo, context.dataFlowInfo) private fun noChange(context: ExpressionTypingContext) = ConditionalDataFlowInfo(context.dataFlowInfo)
/* /*
* (a: SubjectType) is Type * (a: SubjectType) is Type
*/ */
private fun checkTypeCompatibility( private fun checkTypeCompatibility(
context: ExpressionTypingContext, context: ExpressionTypingContext,
type: KotlinType?, type: KotlinType,
subjectType: KotlinType, subjectType: KotlinType,
reportErrorOn: KtElement reportErrorOn: KtElement
) { ) {
// TODO : Take smart casts into account? // TODO : Take smart casts into account?
if (type == null) {
return
}
if (TypeIntersector.isIntersectionEmpty(type, subjectType)) { if (TypeIntersector.isIntersectionEmpty(type, subjectType)) {
context.trace.report(INCOMPATIBLE_TYPES.on(reportErrorOn, type, subjectType)) context.trace.report(INCOMPATIBLE_TYPES.on(reportErrorOn, type, subjectType))
return return
@@ -22,7 +22,7 @@ val wxx1 = <!NO_ELSE_IN_WHEN!>when<!> { true -> 42 }
val wxx2: Unit = <!NO_ELSE_IN_WHEN!>when<!> { true -> <!CONSTANT_EXPECTED_TYPE_MISMATCH!>42<!> } val wxx2: Unit = <!NO_ELSE_IN_WHEN!>when<!> { true -> <!CONSTANT_EXPECTED_TYPE_MISMATCH!>42<!> }
val wxx3 = idAny(<!NO_ELSE_IN_WHEN!>when<!> { true -> 42 }) val wxx3 = idAny(<!NO_ELSE_IN_WHEN!>when<!> { true -> 42 })
val wxx4 = id(<!NO_ELSE_IN_WHEN!>when<!> { true -> 42 }) val wxx4 = id(<!NO_ELSE_IN_WHEN!>when<!> { true -> 42 })
val wxx5 = idUnit(<!NO_ELSE_IN_WHEN!>when<!> { true -> 42 }) val wxx5 = idUnit(<!NO_ELSE_IN_WHEN!>when<!> { true -> <!CONSTANT_EXPECTED_TYPE_MISMATCH!>42<!> })
val wxx6 = null ?: <!NO_ELSE_IN_WHEN!>when<!> { true -> 42 } val wxx6 = null ?: <!NO_ELSE_IN_WHEN!>when<!> { true -> 42 }
val wxx7 = "" + <!NO_ELSE_IN_WHEN!>when<!> { true -> 42 } val wxx7 = "" + <!NO_ELSE_IN_WHEN!>when<!> { true -> 42 }
@@ -4,8 +4,8 @@ fun fn(c: Char?): Any? =
if (c == null) TODO() if (c == null) TODO()
else when (<!DEBUG_INFO_SMARTCAST!>c<!>) { else when (<!DEBUG_INFO_SMARTCAST!>c<!>) {
'a' -> when (<!DEBUG_INFO_SMARTCAST!>c<!>) { 'a' -> when (<!DEBUG_INFO_SMARTCAST!>c<!>) {
'B' -> <!IMPLICIT_CAST_TO_ANY!>1<!> 'B' -> 1
'C' -> <!IMPLICIT_CAST_TO_ANY!>"sdf"<!> 'C' -> "sdf"
else -> TODO() else -> TODO()
} }
else -> TODO() else -> TODO()
@@ -1,11 +1,10 @@
fun foo(s: Any?): String { fun foo(s: Any?): String {
val t = when { val t = when {
// To resolve: String U Nothing? = String? // To resolve: String U Nothing? = String?
s is String -> s s is String -> <!DEBUG_INFO_SMARTCAST!>s<!>
else -> null else -> null
} ?: "" } ?: ""
// Ideally we should have smart cast to String here return t
return <!TYPE_MISMATCH!>t<!>
} }
fun bar(s: Any?): String { fun bar(s: Any?): String {
@@ -24,10 +23,10 @@ fun bar(s: Any?): String {
fun baz(s: String?, r: String?): String { fun baz(s: String?, r: String?): String {
val t = r ?: when { val t = r ?: when {
s != null -> s s != null -> <!DEBUG_INFO_SMARTCAST!>s<!>
else -> "" else -> ""
} }
return <!DEBUG_INFO_SMARTCAST!>t<!> return t
} }
fun withNull(s: String?): String { fun withNull(s: String?): String {
@@ -6,8 +6,8 @@ fun baz(s: String?): String {
val u: String? = null val u: String? = null
when (u) { when (u) {
null -> "" null -> ""
else -> u else -> <!DEBUG_INFO_SMARTCAST!>u<!>
} }
} }
return <!DEBUG_INFO_SMARTCAST!>t<!> return t
} }
@@ -1,15 +1,15 @@
fun foo(s: String) = s.length fun foo(s: String) = s.length
fun baz(s: String?, r: String?): Int { fun baz(s: String?, r: String?): Int {
return foo(<!DEBUG_INFO_SMARTCAST!>r ?: when { return foo(r ?: when {
s != null -> s s != null -> <!DEBUG_INFO_SMARTCAST!>s<!>
else -> "" else -> ""
}<!>) })
} }
fun bar(s: String?, r: String?): Int { fun bar(s: String?, r: String?): Int {
return <!DEBUG_INFO_SMARTCAST!>(r ?: when { return (r ?: when {
s != null -> s s != null -> <!DEBUG_INFO_SMARTCAST!>s<!>
else -> "" else -> ""
})<!>.length }).length
} }
@@ -1,10 +1,10 @@
// Smart casts on complex expressions // Smart casts on complex expressions
fun baz(s: String?): Int { fun baz(s: String?): Int {
if (s == null) return 0 if (s == null) return 0
return <!DEBUG_INFO_SMARTCAST!>when(<!DEBUG_INFO_SMARTCAST!>s<!>) { return when(<!DEBUG_INFO_SMARTCAST!>s<!>) {
"abc" -> s "abc" -> <!DEBUG_INFO_SMARTCAST!>s<!>
else -> "xyz" else -> "xyz"
}<!>.length }.length
} }
var ss: String? = null var ss: String? = null
@@ -3,16 +3,16 @@ fun baz(s: String?): String {
// if explicit type String is given for t, problem disappears // if explicit type String is given for t, problem disappears
val t = when(<!DEBUG_INFO_SMARTCAST!>s<!>) { val t = when(<!DEBUG_INFO_SMARTCAST!>s<!>) {
// !! is detected as unnecessary here // !! is detected as unnecessary here
"abc" -> s "abc" -> <!DEBUG_INFO_SMARTCAST!>s<!>
else -> "xyz" else -> "xyz"
} }
return <!DEBUG_INFO_SMARTCAST!>t<!> return t
} }
fun foo(s: String?): String { fun foo(s: String?): String {
val t = when { val t = when {
s != null -> s s != null -> <!DEBUG_INFO_SMARTCAST!>s<!>
else -> "" else -> ""
} }
return <!DEBUG_INFO_SMARTCAST!>t<!> return t
} }
@@ -1,11 +1,11 @@
fun foo(s: Any): String { fun foo(s: Any): String {
val x = when (s) { val x = when (s) {
is String -> s is String -> <!DEBUG_INFO_SMARTCAST!>s<!>
is Int -> "$s" is Int -> "$s"
else -> return "" else -> return ""
} }
val y: String = <!DEBUG_INFO_SMARTCAST!>x<!> // should be Ok val y: String = x // should be Ok
return y return y
} }
+12
View File
@@ -0,0 +1,12 @@
fun foo(x: Int) = x
fun test0(flag: Boolean) {
foo(if (flag) <!CONSTANT_EXPECTED_TYPE_MISMATCH!>true<!> else <!TYPE_MISMATCH!>""<!>)
}
fun test1(flag: Boolean) {
foo(when (flag) {
true -> <!CONSTANT_EXPECTED_TYPE_MISMATCH!>true<!>
else -> <!TYPE_MISMATCH!>""<!>
})
}
+5
View File
@@ -0,0 +1,5 @@
package
public fun foo(/*0*/ x: kotlin.Int): kotlin.Int
public fun test0(/*0*/ flag: kotlin.Boolean): kotlin.Unit
public fun test1(/*0*/ flag: kotlin.Boolean): kotlin.Unit
+9
View File
@@ -0,0 +1,9 @@
val test: Int = if (true) {
when (2) {
1 -> 1
else -> <!NULL_FOR_NONNULL_TYPE!>null<!>
}
}
else {
2
}
+3
View File
@@ -0,0 +1,3 @@
package
public val test: kotlin.Int
+17
View File
@@ -0,0 +1,17 @@
fun test1(): Int {
val x: String = if (true) {
when {
true -> <!TYPE_MISMATCH!>Any()<!>
else -> <!NULL_FOR_NONNULL_TYPE!>null<!>
}
} else ""
return x.hashCode()
}
fun test2(): Int {
val x: String = when {
true -> <!TYPE_MISMATCH!>Any()<!>
else -> null
} ?: return 0
return x.hashCode()
}
+4
View File
@@ -0,0 +1,4 @@
package
public fun test1(): kotlin.Int
public fun test2(): kotlin.Int
@@ -0,0 +1,6 @@
val test: Int = listOf<Any>().map {
when (it) {
is Int -> <!DEBUG_INFO_SMARTCAST!>it<!>
else -> throw AssertionError()
}
}.sum()
@@ -0,0 +1,3 @@
package
public val test: kotlin.Int
@@ -18525,12 +18525,30 @@ public class DiagnosticsTestGenerated extends AbstractDiagnosticsTest {
doTest(fileName); doTest(fileName);
} }
@TestMetadata("kt10439.kt")
public void testKt10439() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/when/kt10439.kt");
doTest(fileName);
}
@TestMetadata("kt4434.kt") @TestMetadata("kt4434.kt")
public void testKt4434() throws Exception { public void testKt4434() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/when/kt4434.kt"); String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/when/kt4434.kt");
doTest(fileName); doTest(fileName);
} }
@TestMetadata("kt9929.kt")
public void testKt9929() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/when/kt9929.kt");
doTest(fileName);
}
@TestMetadata("kt9972.kt")
public void testKt9972() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/when/kt9972.kt");
doTest(fileName);
}
@TestMetadata("NoElseExpectedUnit.kt") @TestMetadata("NoElseExpectedUnit.kt")
public void testNoElseExpectedUnit() throws Exception { public void testNoElseExpectedUnit() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/when/NoElseExpectedUnit.kt"); String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/when/NoElseExpectedUnit.kt");
@@ -1102,6 +1102,12 @@ public class DiagnosticsTestWithStdLibGenerated extends AbstractDiagnosticsTestW
doTest(fileName); doTest(fileName);
} }
@TestMetadata("kt10463.kt")
public void testKt10463() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithStdLib/smartcasts/kt10463.kt");
doTest(fileName);
}
@TestMetadata("lazyDeclaresAndModifies.kt") @TestMetadata("lazyDeclaresAndModifies.kt")
public void testLazyDeclaresAndModifies() throws Exception { public void testLazyDeclaresAndModifies() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithStdLib/smartcasts/lazyDeclaresAndModifies.kt"); String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithStdLib/smartcasts/lazyDeclaresAndModifies.kt");
+2 -2
View File
@@ -1,7 +1,7 @@
fun baz(s: String?): Int { fun baz(s: String?): Int {
if (s == null) return 0 if (s == null) return 0
return <info descr="Smart cast to kotlin.String">when</info>(<info descr="Smart cast to kotlin.String">s</info>) { return when(<info descr="Smart cast to kotlin.String">s</info>) {
"abc" -> s "abc" -> <info descr="Smart cast to kotlin.String">s</info>
else -> "xyz" else -> "xyz"
}.length }.length
} }
+4 -4
View File
@@ -1,15 +1,15 @@
fun foo(s: String) = s.length fun foo(s: String) = s.length
fun baz(s: String?, r: String?): Int { fun baz(s: String?, r: String?): Int {
return foo(r <info descr="Smart cast to kotlin.String">?:</info> when { return foo(r ?: when {
s != null -> s s != null -> <info descr="Smart cast to kotlin.String">s</info>
else -> "" else -> ""
}) })
} }
fun bar(s: String?, r: String?): Int { fun bar(s: String?, r: String?): Int {
return (r <info descr="Smart cast to kotlin.String">?:</info> when { return (r ?: when {
s != null -> s s != null -> <info descr="Smart cast to kotlin.String">s</info>
else -> "" else -> ""
}).length }).length
} }