diff --git a/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/ControlStructureTypingUtils.java b/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/ControlStructureTypingUtils.java index e772cf90599..689d56327c2 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/ControlStructureTypingUtils.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/ControlStructureTypingUtils.java @@ -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 entryDataFlowInfos + ) { + Map 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(); diff --git a/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/DataFlowAnalyzer.java b/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/DataFlowAnalyzer.java index f96ad4f5ea9..3f17f3d2caf 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/DataFlowAnalyzer.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/DataFlowAnalyzer.java @@ -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; diff --git a/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/PatternMatchingTypingVisitor.kt b/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/PatternMatchingTypingVisitor.kt index 16b590d2cd0..ed8a28a962f 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/PatternMatchingTypingVisitor.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/PatternMatchingTypingVisitor.kt @@ -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 { + 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() { + 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 { + 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 { + subjectDataFlowValue: DataFlowValue, + subjectType: KotlinType + ): ArrayList { val subjectExpression = expression.subjectExpression - val thenInfo = Maps.newHashMapWithExpectedSize(expression.entries.size) + val argumentDataFlowInfos = ArrayList() 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 - ): KotlinTypeInfo { - val coercionStrategy = if (isStatement) CoercionStrategy.COERCION_TO_UNIT else CoercionStrategy.NO_COERCION + whenResultValue: DataFlowValue? + ): Pair { + val bindingContext = contextAfterSubject.trace.bindingContext - val expressionTypes = hashSetOf() - 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 diff --git a/compiler/testData/diagnostics/tests/controlStructures/ifWhenWithoutElse.kt b/compiler/testData/diagnostics/tests/controlStructures/ifWhenWithoutElse.kt index f3c625fb63b..b4b093a65ea 100644 --- a/compiler/testData/diagnostics/tests/controlStructures/ifWhenWithoutElse.kt +++ b/compiler/testData/diagnostics/tests/controlStructures/ifWhenWithoutElse.kt @@ -22,7 +22,7 @@ val wxx1 = when { true -> 42 } val wxx2: Unit = when { true -> 42 } val wxx3 = idAny(when { true -> 42 }) val wxx4 = id(when { true -> 42 }) -val wxx5 = idUnit(when { true -> 42 }) +val wxx5 = idUnit(when { true -> 42 }) val wxx6 = null ?: when { true -> 42 } val wxx7 = "" + when { true -> 42 } diff --git a/compiler/testData/diagnostics/tests/controlStructures/kt10706.kt b/compiler/testData/diagnostics/tests/controlStructures/kt10706.kt index 3f742d21165..ceb3b4ea3fe 100644 --- a/compiler/testData/diagnostics/tests/controlStructures/kt10706.kt +++ b/compiler/testData/diagnostics/tests/controlStructures/kt10706.kt @@ -4,8 +4,8 @@ fun fn(c: Char?): Any? = if (c == null) TODO() else when (c) { 'a' -> when (c) { - 'B' -> 1 - 'C' -> "sdf" + 'B' -> 1 + 'C' -> "sdf" else -> TODO() } else -> TODO() diff --git a/compiler/testData/diagnostics/tests/smartCasts/elvisExprNotNull.kt b/compiler/testData/diagnostics/tests/smartCasts/elvisExprNotNull.kt index 7c4f1a0f8b7..17858940a2a 100644 --- a/compiler/testData/diagnostics/tests/smartCasts/elvisExprNotNull.kt +++ b/compiler/testData/diagnostics/tests/smartCasts/elvisExprNotNull.kt @@ -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 -> s else -> null } ?: "" - // Ideally we should have smart cast to String here - return 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 -> s else -> "" } - return t + return t } fun withNull(s: String?): String { diff --git a/compiler/testData/diagnostics/tests/smartCasts/ifWhenExprNonNull.kt b/compiler/testData/diagnostics/tests/smartCasts/ifWhenExprNonNull.kt index f58e9729feb..a1b05930ceb 100644 --- a/compiler/testData/diagnostics/tests/smartCasts/ifWhenExprNonNull.kt +++ b/compiler/testData/diagnostics/tests/smartCasts/ifWhenExprNonNull.kt @@ -6,8 +6,8 @@ fun baz(s: String?): String { val u: String? = null when (u) { null -> "" - else -> u + else -> u } } - return t + return t } diff --git a/compiler/testData/diagnostics/tests/smartCasts/smartCastOnElvis.kt b/compiler/testData/diagnostics/tests/smartCasts/smartCastOnElvis.kt index 4b30b98e488..aeefbcf6d78 100644 --- a/compiler/testData/diagnostics/tests/smartCasts/smartCastOnElvis.kt +++ b/compiler/testData/diagnostics/tests/smartCasts/smartCastOnElvis.kt @@ -1,15 +1,15 @@ fun foo(s: String) = s.length fun baz(s: String?, r: String?): Int { - return foo(r ?: when { - s != null -> s + return foo(r ?: when { + s != null -> s else -> "" - }) + }) } fun bar(s: String?, r: String?): Int { - return (r ?: when { - s != null -> s + return (r ?: when { + s != null -> s else -> "" - }).length + }).length } \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/smartCasts/smartCastOnWhen.kt b/compiler/testData/diagnostics/tests/smartCasts/smartCastOnWhen.kt index ebbe6c09f32..6a3d0d94e87 100644 --- a/compiler/testData/diagnostics/tests/smartCasts/smartCastOnWhen.kt +++ b/compiler/testData/diagnostics/tests/smartCasts/smartCastOnWhen.kt @@ -1,10 +1,10 @@ // Smart casts on complex expressions fun baz(s: String?): Int { if (s == null) return 0 - return when(s) { - "abc" -> s + return when(s) { + "abc" -> s else -> "xyz" - }.length + }.length } var ss: String? = null diff --git a/compiler/testData/diagnostics/tests/smartCasts/whenExprNonNull.kt b/compiler/testData/diagnostics/tests/smartCasts/whenExprNonNull.kt index 46838d28f36..53692fa1bd0 100644 --- a/compiler/testData/diagnostics/tests/smartCasts/whenExprNonNull.kt +++ b/compiler/testData/diagnostics/tests/smartCasts/whenExprNonNull.kt @@ -3,16 +3,16 @@ fun baz(s: String?): String { // if explicit type String is given for t, problem disappears val t = when(s) { // !! is detected as unnecessary here - "abc" -> s + "abc" -> s else -> "xyz" } - return t + return t } fun foo(s: String?): String { val t = when { - s != null -> s + s != null -> s else -> "" } - return t + return t } diff --git a/compiler/testData/diagnostics/tests/when/WhenTypeDisjunctions.kt b/compiler/testData/diagnostics/tests/when/WhenTypeDisjunctions.kt index 437ca0d909d..84e142ee6b8 100644 --- a/compiler/testData/diagnostics/tests/when/WhenTypeDisjunctions.kt +++ b/compiler/testData/diagnostics/tests/when/WhenTypeDisjunctions.kt @@ -1,11 +1,11 @@ fun foo(s: Any): String { val x = when (s) { - is String -> s + is String -> s is Int -> "$s" else -> return "" } - val y: String = x // should be Ok + val y: String = x // should be Ok return y } diff --git a/compiler/testData/diagnostics/tests/when/kt10439.kt b/compiler/testData/diagnostics/tests/when/kt10439.kt new file mode 100644 index 00000000000..e7ca16086a3 --- /dev/null +++ b/compiler/testData/diagnostics/tests/when/kt10439.kt @@ -0,0 +1,12 @@ +fun foo(x: Int) = x + +fun test0(flag: Boolean) { + foo(if (flag) true else "") +} + +fun test1(flag: Boolean) { + foo(when (flag) { + true -> true + else -> "" + }) +} diff --git a/compiler/testData/diagnostics/tests/when/kt10439.txt b/compiler/testData/diagnostics/tests/when/kt10439.txt new file mode 100644 index 00000000000..d843c7314a6 --- /dev/null +++ b/compiler/testData/diagnostics/tests/when/kt10439.txt @@ -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 diff --git a/compiler/testData/diagnostics/tests/when/kt9929.kt b/compiler/testData/diagnostics/tests/when/kt9929.kt new file mode 100644 index 00000000000..30bedd20c98 --- /dev/null +++ b/compiler/testData/diagnostics/tests/when/kt9929.kt @@ -0,0 +1,9 @@ +val test: Int = if (true) { + when (2) { + 1 -> 1 + else -> null + } +} +else { + 2 +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/when/kt9929.txt b/compiler/testData/diagnostics/tests/when/kt9929.txt new file mode 100644 index 00000000000..df8ec4563d5 --- /dev/null +++ b/compiler/testData/diagnostics/tests/when/kt9929.txt @@ -0,0 +1,3 @@ +package + +public val test: kotlin.Int diff --git a/compiler/testData/diagnostics/tests/when/kt9972.kt b/compiler/testData/diagnostics/tests/when/kt9972.kt new file mode 100644 index 00000000000..0711a8ccdbd --- /dev/null +++ b/compiler/testData/diagnostics/tests/when/kt9972.kt @@ -0,0 +1,17 @@ +fun test1(): Int { + val x: String = if (true) { + when { + true -> Any() + else -> null + } + } else "" + return x.hashCode() +} + +fun test2(): Int { + val x: String = when { + true -> Any() + else -> null + } ?: return 0 + return x.hashCode() +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/when/kt9972.txt b/compiler/testData/diagnostics/tests/when/kt9972.txt new file mode 100644 index 00000000000..f92d9808df3 --- /dev/null +++ b/compiler/testData/diagnostics/tests/when/kt9972.txt @@ -0,0 +1,4 @@ +package + +public fun test1(): kotlin.Int +public fun test2(): kotlin.Int diff --git a/compiler/testData/diagnostics/testsWithStdLib/smartcasts/kt10463.kt b/compiler/testData/diagnostics/testsWithStdLib/smartcasts/kt10463.kt new file mode 100644 index 00000000000..a9973ea2f05 --- /dev/null +++ b/compiler/testData/diagnostics/testsWithStdLib/smartcasts/kt10463.kt @@ -0,0 +1,6 @@ +val test: Int = listOf().map { + when (it) { + is Int -> it + else -> throw AssertionError() + } +}.sum() \ No newline at end of file diff --git a/compiler/testData/diagnostics/testsWithStdLib/smartcasts/kt10463.txt b/compiler/testData/diagnostics/testsWithStdLib/smartcasts/kt10463.txt new file mode 100644 index 00000000000..df8ec4563d5 --- /dev/null +++ b/compiler/testData/diagnostics/testsWithStdLib/smartcasts/kt10463.txt @@ -0,0 +1,3 @@ +package + +public val test: kotlin.Int diff --git a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java index 3af04896214..0e9ed2351df 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java @@ -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"); diff --git a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestWithStdLibGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestWithStdLibGenerated.java index 71f0237cfc9..2a517c313ef 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestWithStdLibGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestWithStdLibGenerated.java @@ -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"); diff --git a/idea/testData/checker/infos/SmartCastOnWhen.kt b/idea/testData/checker/infos/SmartCastOnWhen.kt index 5034fb4592c..cce4a33bed3 100644 --- a/idea/testData/checker/infos/SmartCastOnWhen.kt +++ b/idea/testData/checker/infos/SmartCastOnWhen.kt @@ -1,7 +1,7 @@ fun baz(s: String?): Int { if (s == null) return 0 - return when(s) { - "abc" -> s + return when(s) { + "abc" -> s else -> "xyz" }.length } diff --git a/idea/testData/checker/infos/smartCastOnElvis.kt b/idea/testData/checker/infos/smartCastOnElvis.kt index b0e9e62683f..5be2b9e2729 100644 --- a/idea/testData/checker/infos/smartCastOnElvis.kt +++ b/idea/testData/checker/infos/smartCastOnElvis.kt @@ -1,15 +1,15 @@ fun foo(s: String) = s.length fun baz(s: String?, r: String?): Int { - return foo(r ?: when { - s != null -> s + return foo(r ?: when { + s != null -> s else -> "" }) } fun bar(s: String?, r: String?): Int { - return (r ?: when { - s != null -> s + return (r ?: when { + s != null -> s else -> "" }).length } \ No newline at end of file