diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/smartcasts/DataFlowValue.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/smartcasts/DataFlowValue.kt index 840a2965c43..9c827882b7c 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/smartcasts/DataFlowValue.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/smartcasts/DataFlowValue.kt @@ -39,6 +39,8 @@ class DataFlowValue(val identifierInfo: IdentifierInfo, // or protected / public member value from the same module without open / custom getter // Smart casts are completely safe STABLE_VALUE("stable val"), + // Block, or if / else, or when + STABLE_COMPLEX_EXPRESSION("complex expression", ""), // Member value with open / custom getter // Smart casts are not safe PROPERTY_WITH_GETTER("custom getter", "property that has open or custom getter"), @@ -65,7 +67,7 @@ class DataFlowValue(val identifierInfo: IdentifierInfo, * Stable means here we do not expect some sudden change of their values, * like accessing mutable properties in another thread, so smart casts can be used safely. */ - val isStable = (kind == Kind.STABLE_VALUE || kind == Kind.STABLE_VARIABLE) + val isStable = (kind == Kind.STABLE_VALUE || kind == Kind.STABLE_VARIABLE || kind == Kind.STABLE_COMPLEX_EXPRESSION) override fun equals(other: Any?): Boolean { if (this === other) return true diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/smartcasts/DataFlowValueFactory.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/smartcasts/DataFlowValueFactory.kt index 66ed5e1d7c8..099a6e696a5 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/smartcasts/DataFlowValueFactory.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/smartcasts/DataFlowValueFactory.kt @@ -57,6 +57,16 @@ object DataFlowValueFactory { resolutionContext: ResolutionContext<*> ) = createDataFlowValue(expression, type, resolutionContext.trace.bindingContext, resolutionContext.scope.ownerDescriptor) + private fun isComplexExpression(expression: KtExpression): Boolean = when(expression) { + is KtBlockExpression, is KtIfExpression, is KtWhenExpression -> true + is KtBinaryExpression -> expression.operationToken === KtTokens.ELVIS + is KtParenthesizedExpression -> { + val deparenthesized = KtPsiUtil.deparenthesize(expression) + deparenthesized != null && isComplexExpression(deparenthesized) + } + else -> false + } + @JvmStatic fun createDataFlowValue( expression: KtExpression, @@ -85,6 +95,10 @@ object DataFlowValueFactory { Nullability.NOT_NULL) } + if (isComplexExpression(expression)) { + return createDataFlowValueForComplexExpression(expression, type) + } + val result = getIdForStableIdentifier(expression, bindingContext, containingDeclarationOrModule) return DataFlowValue(if (result === IdentifierInfo.NO) ExpressionIdentifierInfo(expression) else result, type) } @@ -124,6 +138,11 @@ object DataFlowValueFactory { bindingContext[BOUND_INITIALIZER_VALUE, variableDescriptor]), variableDescriptor.type) + private fun createDataFlowValueForComplexExpression( + expression: KtExpression, + type: KotlinType + ) = DataFlowValue(ExpressionIdentifierInfo(expression, stableComplex = true), type) + // For only ++ and -- postfix operations private data class PostfixIdentifierInfo(val argumentInfo: IdentifierInfo, val op: KtToken) : IdentifierInfo { override val kind: DataFlowValue.Kind get() = argumentInfo.kind @@ -131,8 +150,13 @@ object DataFlowValueFactory { override fun toString() = "$argumentInfo($op)" } - data class ExpressionIdentifierInfo(val expression: KtExpression) : IdentifierInfo { - override val kind = OTHER + class ExpressionIdentifierInfo(val expression: KtExpression, stableComplex: Boolean = false) : IdentifierInfo { + + override val kind = if (stableComplex) STABLE_COMPLEX_EXPRESSION else OTHER + + override fun equals(other: Any?) = other is ExpressionIdentifierInfo && expression == other.expression + + override fun hashCode() = expression.hashCode() override fun toString() = expression.text ?: "(empty expression)" } diff --git a/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/BasicExpressionTypingVisitor.java b/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/BasicExpressionTypingVisitor.java index dd59a0c43a8..2af3eadccc0 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/BasicExpressionTypingVisitor.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/BasicExpressionTypingVisitor.java @@ -177,7 +177,14 @@ public class BasicExpressionTypingVisitor extends ExpressionTypingVisitor { if (innerExpression == null) { return TypeInfoFactoryKt.noTypeInfo(context); } - return facade.getTypeInfo(innerExpression, context.replaceScope(context.scope)); + KotlinTypeInfo result = facade.getTypeInfo(innerExpression, context.replaceScope(context.scope)); + KotlinType resultType = result.getType(); + if (resultType != null) { + DataFlowValue innerValue = DataFlowValueFactory.createDataFlowValue(innerExpression, resultType, context); + DataFlowValue resultValue = DataFlowValueFactory.createDataFlowValue(expression, resultType, context); + result = result.replaceDataFlowInfo(result.getDataFlowInfo().assign(resultValue, innerValue)); + } + return result; } @Override @@ -1319,8 +1326,10 @@ public class BasicExpressionTypingVisitor extends ExpressionTypingVisitor { } } DataFlowValue resultValue = DataFlowValueFactory.createDataFlowValue(expression, type, context); - dataFlowInfo = dataFlowInfo.disequate(resultValue, nullValue); + dataFlowInfo = dataFlowInfo.assign(resultValue, leftValue).disequate(resultValue, nullValue); if (!jumpInRight) { + DataFlowValue rightValue = DataFlowValueFactory.createDataFlowValue(right, rightType, context); + rightDataFlowInfo = rightDataFlowInfo.assign(resultValue, rightValue); dataFlowInfo = dataFlowInfo.or(rightDataFlowInfo); } } diff --git a/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/ControlStructureTypingVisitor.java b/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/ControlStructureTypingVisitor.java index d3907ef9d09..8de289cc79e 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/ControlStructureTypingVisitor.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/ControlStructureTypingVisitor.java @@ -31,6 +31,8 @@ import org.jetbrains.kotlin.resolve.ModifiersChecker; import org.jetbrains.kotlin.resolve.calls.model.MutableDataFlowInfoForArguments; 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.descriptorUtil.DescriptorUtilsKt; import org.jetbrains.kotlin.resolve.inline.InlineUtil; import org.jetbrains.kotlin.resolve.scopes.LexicalScope; @@ -150,6 +152,13 @@ public class ControlStructureTypingVisitor extends ExpressionTypingVisitor { KotlinType elseType = elseTypeInfo.getType(); DataFlowInfo thenDataFlowInfo = thenTypeInfo.getDataFlowInfo(); DataFlowInfo elseDataFlowInfo = elseTypeInfo.getDataFlowInfo(); + if (resultType != null && thenType != null && elseType != null) { + DataFlowValue resultValue = DataFlowValueFactory.createDataFlowValue(ifExpression, resultType, context); + DataFlowValue thenValue = DataFlowValueFactory.createDataFlowValue(thenBranch, thenType, context); + thenDataFlowInfo = thenDataFlowInfo.assign(resultValue, thenValue); + DataFlowValue elseValue = DataFlowValueFactory.createDataFlowValue(elseBranch, elseType, context); + elseDataFlowInfo = elseDataFlowInfo.assign(resultValue, elseValue); + } loopBreakContinuePossible |= thenTypeInfo.getJumpOutPossible() || elseTypeInfo.getJumpOutPossible(); diff --git a/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/ExpressionTypingServices.java b/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/ExpressionTypingServices.java index bde4d741ee5..57745cd9c12 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/ExpressionTypingServices.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/ExpressionTypingServices.java @@ -30,6 +30,8 @@ import org.jetbrains.kotlin.resolve.*; import org.jetbrains.kotlin.resolve.calls.context.ContextDependency; import org.jetbrains.kotlin.resolve.calls.context.ResolutionContext; 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.scopes.LexicalScope; import org.jetbrains.kotlin.resolve.scopes.LexicalScopeKind; import org.jetbrains.kotlin.resolve.scopes.LexicalWritableScope; @@ -267,6 +269,13 @@ public class ExpressionTypingServices { result = getTypeOfLastExpressionInBlock( statementExpression, newContext.replaceExpectedType(context.expectedType), coercionStrategyForLastExpression, blockLevelVisitor); + if (result.getType() != null && statementExpression.getParent() instanceof KtBlockExpression) { + DataFlowValue lastExpressionValue = DataFlowValueFactory.createDataFlowValue( + statementExpression, result.getType(), context); + DataFlowValue blockExpressionValue = DataFlowValueFactory.createDataFlowValue( + (KtBlockExpression) statementExpression.getParent(), result.getType(), context); + result = result.replaceDataFlowInfo(result.getDataFlowInfo().assign(blockExpressionValue, lastExpressionValue)); + } } else { result = blockLevelVisitor 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 c0767e178e6..6027cbcfa17 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/PatternMatchingTypingVisitor.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/PatternMatchingTypingVisitor.kt @@ -105,8 +105,10 @@ class PatternMatchingTypingVisitor internal constructor(facade: ExpressionTyping val dataFlowInfoForEntries = analyzeConditionsInWhenEntries(expression, contextAfterSubject, subjectDataFlowValue, subjectType) val whenReturnType = inferTypeForWhenExpression(expression, contextWithExpectedType, contextAfterSubject, dataFlowInfoForEntries) + val whenResultValue = whenReturnType?.let { DataFlowValueFactory.createDataFlowValue(expression, it, contextAfterSubject) } - val branchesTypeInfo = joinWhenExpressionBranches(expression, contextAfterSubject, whenReturnType, jumpOutPossibleInSubject) + val branchesTypeInfo = + joinWhenExpressionBranches(expression, contextAfterSubject, whenReturnType, jumpOutPossibleInSubject, whenResultValue) val isExhaustive = WhenChecker.isWhenExhaustive(expression, trace) @@ -191,7 +193,8 @@ class PatternMatchingTypingVisitor internal constructor(facade: ExpressionTyping expression: KtWhenExpression, contextAfterSubject: ExpressionTypingContext, resultType: KotlinType?, - jumpOutPossibleInSubject: Boolean + jumpOutPossibleInSubject: Boolean, + whenResultValue: DataFlowValue? ): KotlinTypeInfo { val bindingContext = contextAfterSubject.trace.bindingContext @@ -208,7 +211,14 @@ class PatternMatchingTypingVisitor internal constructor(facade: ExpressionTyping errorTypeExistInBranch = true } - val entryDataFlowInfo = entryTypeInfo.dataFlowInfo + val entryDataFlowInfo = + if (whenResultValue != null && entryType != null) { + val entryValue = DataFlowValueFactory.createDataFlowValue(entryExpression, entryType, contextAfterSubject) + entryTypeInfo.dataFlowInfo.assign(whenResultValue, entryValue) + } + else { + entryTypeInfo.dataFlowInfo + } currentDataFlowInfo = if (entryType != null && KotlinBuiltIns.isNothing(entryType)) diff --git a/compiler/testData/diagnostics/tests/smartCasts/variables/ifNullAssignment.kt b/compiler/testData/diagnostics/tests/smartCasts/variables/ifNullAssignment.kt index 54d2da42690..831454ca2e1 100644 --- a/compiler/testData/diagnostics/tests/smartCasts/variables/ifNullAssignment.kt +++ b/compiler/testData/diagnostics/tests/smartCasts/variables/ifNullAssignment.kt @@ -3,7 +3,7 @@ fun basic(): String { var current: String? = null current = if (current == null) "bar" else current - return current + return current } fun foo(flag: Boolean) { @@ -13,7 +13,7 @@ fun foo(flag: Boolean) { x = if (flag) "34" else "12" } - x.hashCode() + x.hashCode() } fun bar(flag: Boolean) { @@ -26,7 +26,7 @@ fun bar(flag: Boolean) { } } - x.hashCode() + x.hashCode() } fun baz(flag: Boolean) { @@ -40,7 +40,7 @@ fun baz(flag: Boolean) { } } - x.hashCode() + x.hashCode() } fun gav(flag: Boolean, arg: String?) {