diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/smartcasts/DataFlowValueFactory.java b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/smartcasts/DataFlowValueFactory.java
index 8af1f797bcb..e7189659b90 100644
--- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/smartcasts/DataFlowValueFactory.java
+++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/smartcasts/DataFlowValueFactory.java
@@ -66,6 +66,21 @@ public class DataFlowValueFactory {
resolutionContext.scope.getOwnerDescriptor());
}
+ private static boolean isComplexExpression(@NotNull KtExpression expression) {
+ if (expression instanceof KtBlockExpression ||
+ expression instanceof KtIfExpression ||
+ expression instanceof KtWhenExpression ||
+ (expression instanceof KtBinaryExpression && ((KtBinaryExpression) expression).getOperationToken() == KtTokens.ELVIS)) {
+
+ return true;
+ }
+ if (expression instanceof KtParenthesizedExpression) {
+ KtExpression deparenthesized = KtPsiUtil.deparenthesize(expression);
+ return deparenthesized != null && isComplexExpression(deparenthesized);
+ }
+ return false;
+ }
+
@NotNull
public static DataFlowValue createDataFlowValue(
@NotNull KtExpression expression,
@@ -96,7 +111,7 @@ public class DataFlowValueFactory {
Nullability.NOT_NULL);
}
- if (expression instanceof KtBlockExpression || expression instanceof KtIfExpression || expression instanceof KtWhenExpression) {
+ if (isComplexExpression(expression)) {
return createDataFlowValueForComplexExpression(expression, type);
}
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 f1760f99164..f4e2b8f2821 100644
--- a/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/BasicExpressionTypingVisitor.java
+++ b/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/BasicExpressionTypingVisitor.java
@@ -173,7 +173,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
@@ -1278,19 +1285,30 @@ public class BasicExpressionTypingVisitor extends ExpressionTypingVisitor {
boolean loopBreakContinuePossible = leftTypeInfo.getJumpOutPossible() || rightTypeInfo.getJumpOutPossible();
KotlinType rightType = rightTypeInfo.getType();
- // Only left argument DFA is taken into account here: we cannot be sure that right argument is executed
+ // Only left argument DFA is taken into account here: we cannot be sure that right argument is joined
+ // (we merge it with right DFA if right argument contains no jump outside)
DataFlowInfo dataFlowInfo = resolvedCall.getDataFlowInfoForArguments().getInfo(call.getValueArguments().get(1));
+
+ KotlinType type = resolvedCall.getResultingDescriptor().getReturnType();
+ if (type == null || rightType == null) return TypeInfoFactoryKt.noTypeInfo(dataFlowInfo);
if (leftType != null) {
DataFlowValue leftValue = createDataFlowValue(left, leftType, context);
DataFlowInfo rightDataFlowInfo = resolvedCall.getDataFlowInfoForArguments().getResultInfo();
+ boolean jumpInRight = KotlinBuiltIns.isNothing(rightType);
+ DataFlowValue nullValue = DataFlowValue.nullValue(components.builtIns);
// left argument is considered not-null if it's not-null also in right part or if we have jump in right part
- if ((rightType != null && KotlinBuiltIns.isNothingOrNullableNothing(rightType) && !rightType.isMarkedNullable())
- || !rightDataFlowInfo.getPredictableNullability(leftValue).canBeNull()) {
- dataFlowInfo = dataFlowInfo.disequate(leftValue, DataFlowValue.nullValue(components.builtIns));
+ if (jumpInRight || !rightDataFlowInfo.getPredictableNullability(leftValue).canBeNull()) {
+ dataFlowInfo = dataFlowInfo.disequate(leftValue, nullValue);
+ }
+ DataFlowValue resultValue = DataFlowValueFactory.createDataFlowValue(expression, type, context);
+ 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);
}
}
- KotlinType type = resolvedCall.getResultingDescriptor().getReturnType();
- if (type == null || rightType == null) return TypeInfoFactoryKt.noTypeInfo(dataFlowInfo);
+
// Sometimes return type for special call for elvis operator might be nullable,
// but result is not nullable if the right type is not nullable
diff --git a/compiler/testData/diagnostics/tests/smartCasts/elvisExprNotNull.kt b/compiler/testData/diagnostics/tests/smartCasts/elvisExprNotNull.kt
new file mode 100644
index 00000000000..7c4f1a0f8b7
--- /dev/null
+++ b/compiler/testData/diagnostics/tests/smartCasts/elvisExprNotNull.kt
@@ -0,0 +1,37 @@
+fun foo(s: Any?): String {
+ val t = when {
+ // To resolve: String U Nothing? = String?
+ s is String -> s
+ else -> null
+ } ?: ""
+ // Ideally we should have smart cast to String here
+ return t
+}
+
+fun bar(s: Any?): String {
+ // To resolve: String U Nothing? = String?
+ val t = (if (s == null) {
+ null
+ }
+ else {
+ val u: Any? = null
+ if (u !is String) return ""
+ u
+ }) ?: "xyz"
+ // Ideally we should have smart cast to String here
+ return t
+}
+
+fun baz(s: String?, r: String?): String {
+ val t = r ?: when {
+ s != null -> s
+ else -> ""
+ }
+ return t
+}
+
+fun withNull(s: String?): String {
+ val t = s ?: null
+ // Error: nullable
+ return t
+}
\ No newline at end of file
diff --git a/compiler/testData/diagnostics/tests/smartCasts/elvisExprNotNull.txt b/compiler/testData/diagnostics/tests/smartCasts/elvisExprNotNull.txt
new file mode 100644
index 00000000000..f13a546079c
--- /dev/null
+++ b/compiler/testData/diagnostics/tests/smartCasts/elvisExprNotNull.txt
@@ -0,0 +1,6 @@
+package
+
+public fun bar(/*0*/ s: kotlin.Any?): kotlin.String
+public fun baz(/*0*/ s: kotlin.String?, /*1*/ r: kotlin.String?): kotlin.String
+public fun foo(/*0*/ s: kotlin.Any?): kotlin.String
+public fun withNull(/*0*/ s: kotlin.String?): kotlin.String
diff --git a/compiler/testData/diagnostics/tests/smartCasts/smartCastOnElvis.kt b/compiler/testData/diagnostics/tests/smartCasts/smartCastOnElvis.kt
new file mode 100644
index 00000000000..4b30b98e488
--- /dev/null
+++ b/compiler/testData/diagnostics/tests/smartCasts/smartCastOnElvis.kt
@@ -0,0 +1,15 @@
+fun foo(s: String) = s.length
+
+fun baz(s: String?, r: String?): Int {
+ return foo(r ?: when {
+ s != null -> s
+ else -> ""
+ })
+}
+
+fun bar(s: String?, r: String?): Int {
+ return (r ?: when {
+ s != null -> s
+ else -> ""
+ }).length
+}
\ No newline at end of file
diff --git a/compiler/testData/diagnostics/tests/smartCasts/smartCastOnElvis.txt b/compiler/testData/diagnostics/tests/smartCasts/smartCastOnElvis.txt
new file mode 100644
index 00000000000..d78f59594fb
--- /dev/null
+++ b/compiler/testData/diagnostics/tests/smartCasts/smartCastOnElvis.txt
@@ -0,0 +1,5 @@
+package
+
+public fun bar(/*0*/ s: kotlin.String?, /*1*/ r: kotlin.String?): kotlin.Int
+public fun baz(/*0*/ s: kotlin.String?, /*1*/ r: kotlin.String?): kotlin.Int
+public fun foo(/*0*/ s: kotlin.String): kotlin.Int
diff --git a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java
index e3fb246dab8..84de1fe4a57 100644
--- a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java
+++ b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java
@@ -14907,6 +14907,12 @@ public class DiagnosticsTestGenerated extends AbstractDiagnosticsTest {
doTest(fileName);
}
+ @TestMetadata("elvisExprNotNull.kt")
+ public void testElvisExprNotNull() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/elvisExprNotNull.kt");
+ doTest(fileName);
+ }
+
@TestMetadata("elvisNothingRHS.kt")
public void testElvisNothingRHS() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/elvisNothingRHS.kt");
@@ -15225,6 +15231,12 @@ public class DiagnosticsTestGenerated extends AbstractDiagnosticsTest {
doTest(fileName);
}
+ @TestMetadata("smartCastOnElvis.kt")
+ public void testSmartCastOnElvis() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/smartCastOnElvis.kt");
+ doTest(fileName);
+ }
+
@TestMetadata("smartCastOnIf.kt")
public void testSmartCastOnIf() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/smartCastOnIf.kt");
diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/highlighter/VariablesHighlightingVisitor.java b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/highlighter/VariablesHighlightingVisitor.java
index 05fd607b049..d59ce1a88bb 100644
--- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/highlighter/VariablesHighlightingVisitor.java
+++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/highlighter/VariablesHighlightingVisitor.java
@@ -67,6 +67,27 @@ class VariablesHighlightingVisitor extends AfterAnalysisHighlightingVisitor {
super.visitParameter(parameter);
}
+ @NotNull
+ private static PsiElement getSmartCastTarget(@NotNull KtExpression expression) {
+ PsiElement target = expression;
+ if (target instanceof KtParenthesizedExpression) {
+ target = KtPsiUtil.deparenthesize((KtParenthesizedExpression) target);
+ if (target == null) {
+ target = expression;
+ }
+ }
+ if (target instanceof KtIfExpression) {
+ target = ((KtIfExpression) target).getIfKeyword();
+ }
+ else if (target instanceof KtWhenExpression) {
+ target = ((KtWhenExpression) target).getWhenKeyword();
+ }
+ else if (target instanceof KtBinaryExpression) {
+ target = ((KtBinaryExpression) target).getOperationReference();
+ }
+ return target;
+ }
+
@Override
public void visitExpression(@NotNull KtExpression expression) {
KotlinType implicitSmartCast = bindingContext.get(IMPLICIT_RECEIVER_SMARTCAST, expression);
@@ -84,14 +105,8 @@ class VariablesHighlightingVisitor extends AfterAnalysisHighlightingVisitor {
KotlinType smartCast = bindingContext.get(SMARTCAST, expression);
if (smartCast != null) {
- PsiElement target = expression;
- if (expression instanceof KtIfExpression) {
- target = ((KtIfExpression) expression).getIfKeyword();
- }
- else if (expression instanceof KtWhenExpression) {
- target = ((KtWhenExpression) expression).getWhenKeyword();
- }
- holder.createInfoAnnotation(target, "Smart cast to " + DescriptorRenderer.FQ_NAMES_IN_TYPES.renderType(smartCast))
+ holder.createInfoAnnotation(getSmartCastTarget(expression),
+ "Smart cast to " + DescriptorRenderer.FQ_NAMES_IN_TYPES.renderType(smartCast))
.setTextAttributes(KotlinHighlightingColors.SMART_CAST_VALUE);
}
diff --git a/idea/testData/checker/infos/smartCastOnElvis.kt b/idea/testData/checker/infos/smartCastOnElvis.kt
new file mode 100644
index 00000000000..b0e9e62683f
--- /dev/null
+++ b/idea/testData/checker/infos/smartCastOnElvis.kt
@@ -0,0 +1,15 @@
+fun foo(s: String) = s.length
+
+fun baz(s: String?, r: String?): Int {
+ return foo(r ?: when {
+ s != null -> s
+ else -> ""
+ })
+}
+
+fun bar(s: String?, r: String?): Int {
+ return (r ?: when {
+ s != null -> s
+ else -> ""
+ }).length
+}
\ No newline at end of file
diff --git a/idea/tests/org/jetbrains/kotlin/checkers/PsiCheckerTestGenerated.java b/idea/tests/org/jetbrains/kotlin/checkers/PsiCheckerTestGenerated.java
index 2ef38e9564d..a1410d8e6ca 100644
--- a/idea/tests/org/jetbrains/kotlin/checkers/PsiCheckerTestGenerated.java
+++ b/idea/tests/org/jetbrains/kotlin/checkers/PsiCheckerTestGenerated.java
@@ -724,6 +724,12 @@ public class PsiCheckerTestGenerated extends AbstractPsiCheckerTest {
doTestWithInfos(fileName);
}
+ @TestMetadata("smartCastOnElvis.kt")
+ public void testSmartCastOnElvis() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("idea/testData/checker/infos/smartCastOnElvis.kt");
+ doTestWithInfos(fileName);
+ }
+
@TestMetadata("SmartCastOnIf.kt")
public void testSmartCastOnIf() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/checker/infos/SmartCastOnIf.kt");