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:
+28
-1
@@ -65,7 +65,7 @@ public class ControlStructureTypingUtils {
|
||||
private static final Logger LOG = Logger.getInstance(ControlStructureTypingUtils.class);
|
||||
|
||||
public enum ResolveConstruct {
|
||||
IF("if"), ELVIS("elvis"), EXCL_EXCL("ExclExcl");
|
||||
IF("if"), ELVIS("elvis"), EXCL_EXCL("ExclExcl"), WHEN("when");
|
||||
|
||||
private final String name;
|
||||
|
||||
@@ -188,6 +188,20 @@ public class ControlStructureTypingUtils {
|
||||
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(
|
||||
@NotNull final KtExpression expression,
|
||||
@NotNull final KtExpression calleeExpression,
|
||||
@@ -320,6 +334,19 @@ public class ControlStructureTypingUtils {
|
||||
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
|
||||
public Boolean visitIfExpression(@NotNull KtIfExpression ifExpression, CheckTypeContext c) {
|
||||
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.constants.*;
|
||||
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.TypeUtils;
|
||||
import org.jetbrains.kotlin.types.checker.KotlinTypeChecker;
|
||||
|
||||
+139
-119
@@ -16,29 +16,26 @@
|
||||
|
||||
package org.jetbrains.kotlin.types.expressions
|
||||
|
||||
import com.google.common.collect.Maps
|
||||
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
|
||||
import org.jetbrains.kotlin.builtins.KotlinBuiltIns.isBoolean
|
||||
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.psi.*
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import org.jetbrains.kotlin.resolve.DescriptorUtils
|
||||
import org.jetbrains.kotlin.resolve.TemporaryBindingTrace
|
||||
import org.jetbrains.kotlin.resolve.TypeResolutionContext
|
||||
import org.jetbrains.kotlin.resolve.*
|
||||
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.DataFlowValue
|
||||
import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowValueFactory
|
||||
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.TypeUtils.NO_EXPECTED_TYPE
|
||||
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.utils.addIfNotNull
|
||||
import java.util.*
|
||||
|
||||
class PatternMatchingTypingVisitor internal constructor(facade: ExpressionTypingInternals) : ExpressionTypingVisitor(facade) {
|
||||
|
||||
@@ -47,9 +44,10 @@ class PatternMatchingTypingVisitor internal constructor(facade: ExpressionTyping
|
||||
val leftHandSide = expression.leftHandSide
|
||||
val typeInfo = facade.safeGetTypeInfo(leftHandSide, context.replaceScope(context.scope))
|
||||
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 conditionInfo = checkTypeForIs(context, knownType, expression.typeReference, dataFlowValue).thenInfo
|
||||
val conditionInfo = checkTypeForIs(context, knownType, typeReference, dataFlowValue).thenInfo
|
||||
val newDataFlowInfo = conditionInfo.and(typeInfo.dataFlowInfo)
|
||||
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) =
|
||||
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.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
|
||||
val subjectExpression = expression.subjectExpression
|
||||
|
||||
val contextAfterSubject: ExpressionTypingContext
|
||||
val subjectType: KotlinType
|
||||
val subjectDataFlowValue: DataFlowValue
|
||||
val jumpOutPossible: Boolean
|
||||
val subjectTypeInfo = subjectExpression?.let { facade.getTypeInfo(it, contextBeforeSubject) }
|
||||
val contextAfterSubject = subjectTypeInfo?.let { contextBeforeSubject.replaceDataFlowInfo(it.dataFlowInfo) } ?: contextBeforeSubject
|
||||
val subjectType = subjectTypeInfo?.type ?: ErrorUtils.createErrorType("Unknown type")
|
||||
val jumpOutPossibleInSubject: Boolean = subjectTypeInfo?.jumpOutPossible ?: false
|
||||
val subjectDataFlowValue = subjectExpression?.let {
|
||||
DataFlowValueFactory.createDataFlowValue(it, subjectType, contextAfterSubject)
|
||||
} ?: DataFlowValue.nullValue(components.builtIns)
|
||||
|
||||
if (subjectExpression == null) {
|
||||
subjectType = ErrorUtils.createErrorType("Unknown type")
|
||||
subjectDataFlowValue = DataFlowValue.nullValue(components.builtIns)
|
||||
contextAfterSubject = contextBeforeSubject
|
||||
jumpOutPossible = false
|
||||
}
|
||||
else {
|
||||
val subjectTypeInfo = facade.safeGetTypeInfo(subjectExpression, contextBeforeSubject)
|
||||
checkSmartCastsInSubjectIfRequired(expression, contextBeforeSubject, subjectType)
|
||||
|
||||
contextAfterSubject = contextBeforeSubject.replaceDataFlowInfo(subjectTypeInfo.dataFlowInfo)
|
||||
subjectType = subjectTypeInfo.type!!
|
||||
subjectDataFlowValue = DataFlowValueFactory.createDataFlowValue(subjectExpression, subjectType, contextAfterSubject)
|
||||
jumpOutPossible = subjectTypeInfo.jumpOutPossible
|
||||
val resolvedCall = resolveSpecialCallForWhen(expression, contextWithExpectedType, contextAfterSubject, subjectDataFlowValue, subjectType)
|
||||
|
||||
if (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()
|
||||
}
|
||||
}
|
||||
}
|
||||
val whenReturnType = resolvedCall.resultingDescriptor.returnType
|
||||
val whenResultValue = whenReturnType?.let { DataFlowValueFactory.createDataFlowValue(expression, it, contextAfterSubject) }
|
||||
|
||||
val (outputDataFlowInfo, jumpOutPossible) =
|
||||
joinWhenExpressionBranches(expression, contextAfterSubject, jumpOutPossibleInSubject, whenResultValue)
|
||||
|
||||
val dataFlowInfoBeforeEntryBodies = collectDataFlowInfoBeforeEntryBodies(expression, contextAfterSubject, subjectType, subjectDataFlowValue)
|
||||
val isExhaustive = WhenChecker.isWhenExhaustive(expression, contextAfterSubject.trace)
|
||||
|
||||
return getTypeInfoForIncompleteWhen(expression, isStatement, isExhaustive, contextWithExpectedType,
|
||||
contextAfterSubject, jumpOutPossible, dataFlowInfoBeforeEntryBodies)
|
||||
val resultDataFlowInfo = 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
|
||||
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,
|
||||
contextAfterSubject: ExpressionTypingContext,
|
||||
subjectType: KotlinType,
|
||||
subjectDataFlowValue: DataFlowValue
|
||||
): Map<KtWhenEntry, DataFlowInfo> {
|
||||
subjectDataFlowValue: DataFlowValue,
|
||||
subjectType: KotlinType
|
||||
): ArrayList<DataFlowInfo> {
|
||||
val subjectExpression = expression.subjectExpression
|
||||
val thenInfo = Maps.newHashMapWithExpectedSize<KtWhenEntry, DataFlowInfo>(expression.entries.size)
|
||||
|
||||
val argumentDataFlowInfos = ArrayList<DataFlowInfo>()
|
||||
var inputDataFlowInfo = contextAfterSubject.dataFlowInfo
|
||||
for (whenEntry in expression.entries) {
|
||||
val conditionsInfo = analyzeWhenEntryConditions(whenEntry,
|
||||
contextAfterSubject.replaceDataFlowInfo(inputDataFlowInfo),
|
||||
subjectExpression, subjectType, subjectDataFlowValue)
|
||||
thenInfo[whenEntry] = conditionsInfo.thenInfo
|
||||
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,
|
||||
isStatement: Boolean,
|
||||
isExhaustive: Boolean,
|
||||
contextWithExpectedType: ExpressionTypingContext,
|
||||
contextAfterSubject: ExpressionTypingContext,
|
||||
jumpOutPossibleInSubject: Boolean,
|
||||
dataFlowBeforeEntryBody: Map<KtWhenEntry, DataFlowInfo>
|
||||
): KotlinTypeInfo {
|
||||
val coercionStrategy = if (isStatement) CoercionStrategy.COERCION_TO_UNIT else CoercionStrategy.NO_COERCION
|
||||
whenResultValue: DataFlowValue?
|
||||
): Pair<DataFlowInfo, Boolean> {
|
||||
val bindingContext = contextAfterSubject.trace.bindingContext
|
||||
|
||||
val expressionTypes = hashSetOf<KotlinType>()
|
||||
var commonDataFlowInfo: DataFlowInfo? = null
|
||||
var currentDataFlowInfo: DataFlowInfo? = null
|
||||
var jumpOutPossible = jumpOutPossibleInSubject
|
||||
val whenValue = DataFlowValueFactory.createDataFlowValue(expression, components.builtIns.nullableAnyType, contextAfterSubject)
|
||||
|
||||
for (whenEntry in expression.entries) {
|
||||
val ifTrueInfo = dataFlowBeforeEntryBody[whenEntry]!!
|
||||
val entryExpression = whenEntry.expression ?: continue
|
||||
|
||||
val bodyExpression = whenEntry.expression
|
||||
if (bodyExpression != null) {
|
||||
val scopeToExtend = newWritableScopeImpl(contextAfterSubject, LexicalScopeKind.WHEN)
|
||||
val contextForEntry = contextWithExpectedType.replaceScope(scopeToExtend).replaceDataFlowInfo(ifTrueInfo).replaceContextDependency(INDEPENDENT)
|
||||
val entryTypeInfo = components.expressionTypingServices.getBlockReturnedTypeWithWritableScope(
|
||||
scopeToExtend, listOf(bodyExpression), coercionStrategy, contextForEntry)
|
||||
val entryTypeInfo = BindingContextUtils.getRecordedTypeInfo(entryExpression, bindingContext) ?:
|
||||
throw AssertionError("When entry was not processed")
|
||||
val entryType = entryTypeInfo.type
|
||||
|
||||
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) {
|
||||
val entryValue = DataFlowValueFactory.createDataFlowValue(bodyExpression, entryType, contextAfterSubject)
|
||||
entryTypeInfo.dataFlowInfo.assign(whenValue, entryValue)
|
||||
}
|
||||
else entryTypeInfo.dataFlowInfo
|
||||
return Pair(currentDataFlowInfo ?: contextAfterSubject.dataFlowInfo, jumpOutPossible)
|
||||
}
|
||||
|
||||
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(
|
||||
@@ -232,14 +261,14 @@ class PatternMatchingTypingVisitor internal constructor(facade: ExpressionTyping
|
||||
if (subjectExpression == null) {
|
||||
context.trace.report(EXPECTED_CONDITION.on(condition))
|
||||
val dataFlowInfo = facade.getTypeInfo(rangeExpression, context).dataFlowInfo
|
||||
newDataFlowInfo = ConditionalDataFlowInfo(dataFlowInfo, dataFlowInfo)
|
||||
newDataFlowInfo = ConditionalDataFlowInfo(dataFlowInfo)
|
||||
return
|
||||
}
|
||||
val argumentForSubject = CallMaker.makeExternalValueArgument(subjectExpression)
|
||||
val typeInfo = facade.checkInExpression(condition, condition.operationReference,
|
||||
argumentForSubject, rangeExpression, context)
|
||||
val dataFlowInfo = typeInfo.dataFlowInfo
|
||||
newDataFlowInfo = ConditionalDataFlowInfo(dataFlowInfo, dataFlowInfo)
|
||||
newDataFlowInfo = ConditionalDataFlowInfo(dataFlowInfo)
|
||||
val type = typeInfo.type
|
||||
if (type == null || !isBoolean(type)) {
|
||||
context.trace.report(TYPE_MISMATCH_IN_RANGE.on(condition))
|
||||
@@ -250,8 +279,9 @@ class PatternMatchingTypingVisitor internal constructor(facade: ExpressionTyping
|
||||
if (subjectExpression == null) {
|
||||
context.trace.report(EXPECTED_CONDITION.on(condition))
|
||||
}
|
||||
if (condition.typeReference != null) {
|
||||
val result = checkTypeForIs(context, subjectType, condition.typeReference, subjectDataFlowValue)
|
||||
val typeReference = condition.typeReference
|
||||
if (typeReference != null) {
|
||||
val result = checkTypeForIs(context, subjectType, typeReference, subjectDataFlowValue)
|
||||
if (condition.isNegated) {
|
||||
newDataFlowInfo = ConditionalDataFlowInfo(result.elseInfo, result.thenInfo)
|
||||
}
|
||||
@@ -264,8 +294,8 @@ class PatternMatchingTypingVisitor internal constructor(facade: ExpressionTyping
|
||||
override fun visitWhenConditionWithExpression(condition: KtWhenConditionWithExpression) {
|
||||
val expression = condition.expression
|
||||
if (expression != null) {
|
||||
newDataFlowInfo = checkTypeForExpressionCondition(context, expression, subjectType, subjectExpression == null,
|
||||
subjectDataFlowValue)
|
||||
newDataFlowInfo = checkTypeForExpressionCondition(
|
||||
context, expression, subjectType, subjectExpression == null, subjectDataFlowValue)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -280,15 +310,12 @@ class PatternMatchingTypingVisitor internal constructor(facade: ExpressionTyping
|
||||
|
||||
private fun checkTypeForExpressionCondition(
|
||||
context: ExpressionTypingContext,
|
||||
expression: KtExpression?,
|
||||
expression: KtExpression,
|
||||
subjectType: KotlinType,
|
||||
conditionExpected: Boolean,
|
||||
subjectDataFlowValue: DataFlowValue
|
||||
): ConditionalDataFlowInfo {
|
||||
var newContext = context
|
||||
if (expression == null) {
|
||||
return noChange(newContext)
|
||||
}
|
||||
val typeInfo = facade.getTypeInfo(expression, newContext)
|
||||
val type = typeInfo.type ?: return noChange(newContext)
|
||||
newContext = newContext.replaceDataFlowInfo(typeInfo.dataFlowInfo)
|
||||
@@ -306,22 +333,18 @@ class PatternMatchingTypingVisitor internal constructor(facade: ExpressionTyping
|
||||
}
|
||||
checkTypeCompatibility(newContext, type, subjectType, expression)
|
||||
val expressionDataFlowValue = DataFlowValueFactory.createDataFlowValue(expression, type, newContext)
|
||||
var result = noChange(newContext)
|
||||
result = ConditionalDataFlowInfo(
|
||||
val result = noChange(newContext)
|
||||
return ConditionalDataFlowInfo(
|
||||
result.thenInfo.equate(subjectDataFlowValue, expressionDataFlowValue),
|
||||
result.elseInfo.disequate(subjectDataFlowValue, expressionDataFlowValue))
|
||||
return result
|
||||
}
|
||||
|
||||
private fun checkTypeForIs(
|
||||
context: ExpressionTypingContext,
|
||||
subjectType: KotlinType,
|
||||
typeReferenceAfterIs: KtTypeReference?,
|
||||
typeReferenceAfterIs: KtTypeReference,
|
||||
subjectDataFlowValue: DataFlowValue
|
||||
): ConditionalDataFlowInfo {
|
||||
if (typeReferenceAfterIs == null) {
|
||||
return noChange(context)
|
||||
}
|
||||
val typeResolutionContext = TypeResolutionContext(context.scope, context.trace, true, /*allowBareTypes=*/ true)
|
||||
val possiblyBareTarget = components.typeResolver.resolvePossiblyBareType(typeResolutionContext, typeReferenceAfterIs)
|
||||
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)
|
||||
}
|
||||
|
||||
private fun noChange(context: ExpressionTypingContext) = ConditionalDataFlowInfo(context.dataFlowInfo, context.dataFlowInfo)
|
||||
private fun noChange(context: ExpressionTypingContext) = ConditionalDataFlowInfo(context.dataFlowInfo)
|
||||
|
||||
/*
|
||||
* (a: SubjectType) is Type
|
||||
*/
|
||||
private fun checkTypeCompatibility(
|
||||
context: ExpressionTypingContext,
|
||||
type: KotlinType?,
|
||||
type: KotlinType,
|
||||
subjectType: KotlinType,
|
||||
reportErrorOn: KtElement
|
||||
) {
|
||||
// TODO : Take smart casts into account?
|
||||
if (type == null) {
|
||||
return
|
||||
}
|
||||
if (TypeIntersector.isIntersectionEmpty(type, subjectType)) {
|
||||
context.trace.report(INCOMPATIBLE_TYPES.on(reportErrorOn, type, subjectType))
|
||||
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 wxx3 = idAny(<!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 wxx7 = "" + <!NO_ELSE_IN_WHEN!>when<!> { true -> 42 }
|
||||
|
||||
|
||||
@@ -4,8 +4,8 @@ fun fn(c: Char?): Any? =
|
||||
if (c == null) TODO()
|
||||
else when (<!DEBUG_INFO_SMARTCAST!>c<!>) {
|
||||
'a' -> when (<!DEBUG_INFO_SMARTCAST!>c<!>) {
|
||||
'B' -> <!IMPLICIT_CAST_TO_ANY!>1<!>
|
||||
'C' -> <!IMPLICIT_CAST_TO_ANY!>"sdf"<!>
|
||||
'B' -> 1
|
||||
'C' -> "sdf"
|
||||
else -> TODO()
|
||||
}
|
||||
else -> TODO()
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
fun foo(s: Any?): String {
|
||||
val t = when {
|
||||
// To resolve: String U Nothing? = String?
|
||||
s is String -> s
|
||||
s is String -> <!DEBUG_INFO_SMARTCAST!>s<!>
|
||||
else -> null
|
||||
} ?: ""
|
||||
// Ideally we should have smart cast to String here
|
||||
return <!TYPE_MISMATCH!>t<!>
|
||||
return t
|
||||
}
|
||||
|
||||
fun bar(s: Any?): String {
|
||||
@@ -24,10 +23,10 @@ fun bar(s: Any?): String {
|
||||
|
||||
fun baz(s: String?, r: String?): String {
|
||||
val t = r ?: when {
|
||||
s != null -> s
|
||||
s != null -> <!DEBUG_INFO_SMARTCAST!>s<!>
|
||||
else -> ""
|
||||
}
|
||||
return <!DEBUG_INFO_SMARTCAST!>t<!>
|
||||
return t
|
||||
}
|
||||
|
||||
fun withNull(s: String?): String {
|
||||
|
||||
@@ -6,8 +6,8 @@ fun baz(s: String?): String {
|
||||
val u: String? = null
|
||||
when (u) {
|
||||
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 baz(s: String?, r: String?): Int {
|
||||
return foo(<!DEBUG_INFO_SMARTCAST!>r ?: when {
|
||||
s != null -> s
|
||||
return foo(r ?: when {
|
||||
s != null -> <!DEBUG_INFO_SMARTCAST!>s<!>
|
||||
else -> ""
|
||||
}<!>)
|
||||
})
|
||||
}
|
||||
|
||||
fun bar(s: String?, r: String?): Int {
|
||||
return <!DEBUG_INFO_SMARTCAST!>(r ?: when {
|
||||
s != null -> s
|
||||
return (r ?: when {
|
||||
s != null -> <!DEBUG_INFO_SMARTCAST!>s<!>
|
||||
else -> ""
|
||||
})<!>.length
|
||||
}).length
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
// Smart casts on complex expressions
|
||||
fun baz(s: String?): Int {
|
||||
if (s == null) return 0
|
||||
return <!DEBUG_INFO_SMARTCAST!>when(<!DEBUG_INFO_SMARTCAST!>s<!>) {
|
||||
"abc" -> s
|
||||
return when(<!DEBUG_INFO_SMARTCAST!>s<!>) {
|
||||
"abc" -> <!DEBUG_INFO_SMARTCAST!>s<!>
|
||||
else -> "xyz"
|
||||
}<!>.length
|
||||
}.length
|
||||
}
|
||||
|
||||
var ss: String? = null
|
||||
|
||||
@@ -3,16 +3,16 @@ fun baz(s: String?): String {
|
||||
// if explicit type String is given for t, problem disappears
|
||||
val t = when(<!DEBUG_INFO_SMARTCAST!>s<!>) {
|
||||
// !! is detected as unnecessary here
|
||||
"abc" -> s
|
||||
"abc" -> <!DEBUG_INFO_SMARTCAST!>s<!>
|
||||
else -> "xyz"
|
||||
}
|
||||
return <!DEBUG_INFO_SMARTCAST!>t<!>
|
||||
return t
|
||||
}
|
||||
|
||||
fun foo(s: String?): String {
|
||||
val t = when {
|
||||
s != null -> s
|
||||
s != null -> <!DEBUG_INFO_SMARTCAST!>s<!>
|
||||
else -> ""
|
||||
}
|
||||
return <!DEBUG_INFO_SMARTCAST!>t<!>
|
||||
return t
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
fun foo(s: Any): String {
|
||||
val x = when (s) {
|
||||
is String -> s
|
||||
is String -> <!DEBUG_INFO_SMARTCAST!>s<!>
|
||||
is Int -> "$s"
|
||||
else -> return ""
|
||||
}
|
||||
|
||||
val y: String = <!DEBUG_INFO_SMARTCAST!>x<!> // should be Ok
|
||||
val y: String = x // should be Ok
|
||||
return y
|
||||
}
|
||||
|
||||
|
||||
@@ -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!>""<!>
|
||||
})
|
||||
}
|
||||
@@ -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
|
||||
@@ -0,0 +1,9 @@
|
||||
val test: Int = if (true) {
|
||||
when (2) {
|
||||
1 -> 1
|
||||
else -> <!NULL_FOR_NONNULL_TYPE!>null<!>
|
||||
}
|
||||
}
|
||||
else {
|
||||
2
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
package
|
||||
|
||||
public val test: kotlin.Int
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@TestMetadata("kt10439.kt")
|
||||
public void testKt10439() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/when/kt10439.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("kt4434.kt")
|
||||
public void testKt4434() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/when/kt4434.kt");
|
||||
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")
|
||||
public void testNoElseExpectedUnit() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/when/NoElseExpectedUnit.kt");
|
||||
|
||||
@@ -1102,6 +1102,12 @@ public class DiagnosticsTestWithStdLibGenerated extends AbstractDiagnosticsTestW
|
||||
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")
|
||||
public void testLazyDeclaresAndModifies() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithStdLib/smartcasts/lazyDeclaresAndModifies.kt");
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
fun baz(s: String?): Int {
|
||||
if (s == null) return 0
|
||||
return <info descr="Smart cast to kotlin.String">when</info>(<info descr="Smart cast to kotlin.String">s</info>) {
|
||||
"abc" -> s
|
||||
return when(<info descr="Smart cast to kotlin.String">s</info>) {
|
||||
"abc" -> <info descr="Smart cast to kotlin.String">s</info>
|
||||
else -> "xyz"
|
||||
}.length
|
||||
}
|
||||
|
||||
+4
-4
@@ -1,15 +1,15 @@
|
||||
fun foo(s: String) = s.length
|
||||
|
||||
fun baz(s: String?, r: String?): Int {
|
||||
return foo(r <info descr="Smart cast to kotlin.String">?:</info> when {
|
||||
s != null -> s
|
||||
return foo(r ?: when {
|
||||
s != null -> <info descr="Smart cast to kotlin.String">s</info>
|
||||
else -> ""
|
||||
})
|
||||
}
|
||||
|
||||
fun bar(s: String?, r: String?): Int {
|
||||
return (r <info descr="Smart cast to kotlin.String">?:</info> when {
|
||||
s != null -> s
|
||||
return (r ?: when {
|
||||
s != null -> <info descr="Smart cast to kotlin.String">s</info>
|
||||
else -> ""
|
||||
}).length
|
||||
}
|
||||
Reference in New Issue
Block a user