diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/CandidateResolver.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/CandidateResolver.kt index e5f891d9518..808ea08a8eb 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/CandidateResolver.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/CandidateResolver.kt @@ -50,8 +50,6 @@ import org.jetbrains.kotlin.resolve.scopes.receivers.ReceiverValue import org.jetbrains.kotlin.types.* import org.jetbrains.kotlin.types.TypeUtils.noExpectedType import org.jetbrains.kotlin.types.checker.KotlinTypeChecker -import org.jetbrains.kotlin.types.expressions.ExpressionTypingUtils -import org.jetbrains.kotlin.utils.sure import java.util.* public class CandidateResolver( @@ -60,7 +58,7 @@ public class CandidateResolver( private val reflectionTypes: ReflectionTypes, private val additionalTypeCheckers: Iterable, private val smartCastManager: SmartCastManager -){ +) { public fun performResolutionForCandidateCall( context: CallCandidateResolutionContext, @@ -451,10 +449,19 @@ public class CandidateResolver( val smartCastNeeded = !ArgumentTypeResolver.isSubtypeOfForArgumentType(receiverArgument.type, expectedReceiverParameterType) var reportUnsafeCall = false - if (smartCastNeeded) { + val dataFlowValue = DataFlowValueFactory.createDataFlowValue(receiverArgument, this) + val nullability = dataFlowInfo.getPredictableNullability(dataFlowValue) + val expression = (receiverArgument as? ExpressionReceiver)?.expression + if (nullability.canBeNull() && !nullability.canBeNonNull()) { + if (!TypeUtils.isNullableType(expectedReceiverParameterType)) { + reportUnsafeCall = true + } + if (dataFlowValue.immanentNullability.canBeNonNull()) { + expression?.let { trace.record(BindingContext.SMARTCAST_NULL, it) } + } + } + else if (smartCastNeeded) { // Look if smart cast has some useful nullability info - val expression = (receiverArgument as? ExpressionReceiver)?.expression - val dataFlowValue = DataFlowValueFactory.createDataFlowValue(receiverArgument, this) val smartCastResult = SmartCastManager.checkAndRecordPossibleCast( dataFlowValue, expectedReceiverParameterType, expression, this, candidateCall.call.calleeExpression, /*recordType =*/true 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 92b0904afc3..3a2e574f2ba 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/BasicExpressionTypingVisitor.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/BasicExpressionTypingVisitor.java @@ -98,12 +98,24 @@ public class BasicExpressionTypingVisitor extends ExpressionTypingVisitor { super(facade); } - private static boolean isLValue(@NotNull KtSimpleNameExpression expression) { + private static boolean isLValueOrUnsafeReceiver(@NotNull KtSimpleNameExpression expression) { PsiElement parent = PsiTreeUtil.skipParentsOfType(expression, KtParenthesizedExpression.class); - if (!(parent instanceof KtBinaryExpression)) return false; - KtBinaryExpression binaryExpression = (KtBinaryExpression) parent; - if (!KtTokens.ALL_ASSIGNMENTS.contains(binaryExpression.getOperationToken())) return false; - return PsiTreeUtil.isAncestor(binaryExpression.getLeft(), expression, false); + if (parent instanceof KtQualifiedExpression) { + KtQualifiedExpression qualifiedExpression = (KtQualifiedExpression) parent; + // See KT-10175: receiver of unsafe call is always not-null at resolver + // so we have to analyze its nullability here + return qualifiedExpression.getOperationSign() == KtTokens.DOT && + qualifiedExpression.getReceiverExpression() == KtPsiUtil.deparenthesize(expression); + } + if (parent instanceof KtBinaryExpression) { + KtBinaryExpression binaryExpression = (KtBinaryExpression) parent; + if (!OperatorConventions.BINARY_OPERATION_NAMES.containsKey(binaryExpression.getOperationToken()) && + !KtTokens.ALL_ASSIGNMENTS.contains(binaryExpression.getOperationToken())) { + return false; + } + return PsiTreeUtil.isAncestor(binaryExpression.getLeft(), expression, false); + } + return false; } private static boolean isDangerousWithNull(@NotNull KtSimpleNameExpression expression, @NotNull ExpressionTypingContext context) { @@ -120,12 +132,6 @@ public class BasicExpressionTypingVisitor extends ExpressionTypingVisitor { return type != null && !type.isMarkedNullable() && binaryExpression.getOperationReference().getReferencedNameElementType() == KtTokens.AS_KEYWORD; } - // . is also sensitive, but at receiver only and not for extension functions with nullable receiver - if (parent instanceof KtQualifiedExpression) { - KtQualifiedExpression qualifiedExpression = (KtQualifiedExpression) parent; - return qualifiedExpression.getOperationSign() == KtTokens.DOT && - qualifiedExpression.getReceiverExpression() == KtPsiUtil.deparenthesize(expression); - } return false; } @@ -134,7 +140,8 @@ public class BasicExpressionTypingVisitor extends ExpressionTypingVisitor { @NotNull ExpressionTypingContext context, @Nullable KotlinType type ) { - if (type != null && !type.isError() && !isLValue(expression)) { + // Receivers are normally analyzed at resolve, with an exception of KT-10175 + if (type != null && !type.isError() && !isLValueOrUnsafeReceiver(expression)) { DataFlowValue dataFlowValue = DataFlowValueFactory.createDataFlowValue(expression, type, context); Nullability nullability = context.dataFlowInfo.getPredictableNullability(dataFlowValue); if (!nullability.canBeNonNull() && nullability.canBeNull()) { diff --git a/compiler/testData/diagnostics/tests/BinaryCallsOnNullableValues.kt b/compiler/testData/diagnostics/tests/BinaryCallsOnNullableValues.kt index 3588d1586ca..7bef37310d9 100644 --- a/compiler/testData/diagnostics/tests/BinaryCallsOnNullableValues.kt +++ b/compiler/testData/diagnostics/tests/BinaryCallsOnNullableValues.kt @@ -8,7 +8,7 @@ fun f(): Unit { x + 1 x plus 1 x < 1 - x += 1 + x += 1 x == 1 x != 1 diff --git a/compiler/testData/diagnostics/tests/Nullability.kt b/compiler/testData/diagnostics/tests/Nullability.kt index 1edb3cf4d97..19791174e2d 100644 --- a/compiler/testData/diagnostics/tests/Nullability.kt +++ b/compiler/testData/diagnostics/tests/Nullability.kt @@ -146,7 +146,7 @@ fun test() { while (out2 == null) { out2?.println(); - out2.println(); + out2.println(); } out2.println() diff --git a/compiler/testData/diagnostics/tests/smartCasts/alwaysNull.kt b/compiler/testData/diagnostics/tests/smartCasts/alwaysNull.kt index 08a5244e77b..2c38c7acdc6 100644 --- a/compiler/testData/diagnostics/tests/smartCasts/alwaysNull.kt +++ b/compiler/testData/diagnostics/tests/smartCasts/alwaysNull.kt @@ -2,12 +2,12 @@ fun foo(): String { var s: String? s = null s?.length - s.length + s.length if (s == null) return s!! var t: String? = "y" if (t == null) t = "x" var x: Int? = null - if (x == null) x += null + if (x == null) x += null return t + s } @@ -15,8 +15,7 @@ fun String?.gav() {} fun bar(s: String?) { if (s != null) return - // Ideally we should have DEBUG_INFO_CONSTANT instead - s.gav() + s.gav() s as? String s as String? s as String diff --git a/compiler/testData/diagnostics/tests/smartCasts/alwaysNullWithJava.kt b/compiler/testData/diagnostics/tests/smartCasts/alwaysNullWithJava.kt index ad0ffd954f7..87c201d66a7 100644 --- a/compiler/testData/diagnostics/tests/smartCasts/alwaysNullWithJava.kt +++ b/compiler/testData/diagnostics/tests/smartCasts/alwaysNullWithJava.kt @@ -10,7 +10,7 @@ public class My { fun test() { val my = My.create() if (my == null) { - my.foo() + my.foo() } } diff --git a/compiler/testData/diagnostics/tests/smartCasts/comparisonUnderAnd.kt b/compiler/testData/diagnostics/tests/smartCasts/comparisonUnderAnd.kt index f712b5356af..c7a783c10d5 100644 --- a/compiler/testData/diagnostics/tests/smartCasts/comparisonUnderAnd.kt +++ b/compiler/testData/diagnostics/tests/smartCasts/comparisonUnderAnd.kt @@ -15,12 +15,12 @@ fun foo(x : String?, y : String?) { else { // y == null but x != y x.length - y.length + y.length } if (y == null && x != y) { // y == null but x != y x.length - y.length + y.length } else { x.length diff --git a/compiler/testData/diagnostics/tests/smartCasts/complexComparison.kt b/compiler/testData/diagnostics/tests/smartCasts/complexComparison.kt index 76e13dbd283..55047be2c31 100644 --- a/compiler/testData/diagnostics/tests/smartCasts/complexComparison.kt +++ b/compiler/testData/diagnostics/tests/smartCasts/complexComparison.kt @@ -6,7 +6,7 @@ fun foo(x: String?, y: String?, z: String?, w: String?) { if (x != null || y != null || (x != z && y != z)) z.length else - z.length + z.length if (x == null || y == null || (x != z && y != z)) z.length else diff --git a/compiler/testData/diagnostics/tests/smartCasts/varnotnull/assignment.kt b/compiler/testData/diagnostics/tests/smartCasts/varnotnull/assignment.kt index 32a19e9b3b1..2b04c9dbd6a 100644 --- a/compiler/testData/diagnostics/tests/smartCasts/varnotnull/assignment.kt +++ b/compiler/testData/diagnostics/tests/smartCasts/varnotnull/assignment.kt @@ -4,7 +4,7 @@ fun foo() { v = "abc" v.length v = null - v.length + v.length v = "abc" v.length } \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/smartCasts/varnotnull/initialization.kt b/compiler/testData/diagnostics/tests/smartCasts/varnotnull/initialization.kt index 30caa4b7066..af06829a8ce 100644 --- a/compiler/testData/diagnostics/tests/smartCasts/varnotnull/initialization.kt +++ b/compiler/testData/diagnostics/tests/smartCasts/varnotnull/initialization.kt @@ -3,5 +3,5 @@ fun foo() { // It is possible in principle to provide smart cast here v.length v = null - v.length + v.length } \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/smartCasts/varnotnull/varNull.kt b/compiler/testData/diagnostics/tests/smartCasts/varnotnull/varNull.kt index e0493bea139..5c474c5f348 100644 --- a/compiler/testData/diagnostics/tests/smartCasts/varnotnull/varNull.kt +++ b/compiler/testData/diagnostics/tests/smartCasts/varnotnull/varNull.kt @@ -1,5 +1,5 @@ fun foo(): Int { var s: String? = "abc" s = null - return s.length + return s.length } \ No newline at end of file diff --git a/compiler/testData/diagnostics/testsWithJsStdLib/dynamicTypes/block.kt b/compiler/testData/diagnostics/testsWithJsStdLib/dynamicTypes/block.kt index c40be6d576a..b596dadb1bd 100644 --- a/compiler/testData/diagnostics/testsWithJsStdLib/dynamicTypes/block.kt +++ b/compiler/testData/diagnostics/testsWithJsStdLib/dynamicTypes/block.kt @@ -9,5 +9,5 @@ fun test() { fun dynamic(body: dynamic.() -> T): T { val topLevel = null - return topLevel.body() + return topLevel.body() } \ No newline at end of file