More accurate handling of "always null" for receivers

This commit is contained in:
Mikhail Glukhikh
2015-11-25 14:14:10 +03:00
parent 89e56093a2
commit 9a6d4c32c5
12 changed files with 45 additions and 32 deletions
@@ -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<AdditionalTypeChecker>,
private val smartCastManager: SmartCastManager
){
) {
public fun <D : CallableDescriptor, F : D> performResolutionForCandidateCall(
context: CallCandidateResolutionContext<D>,
@@ -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
@@ -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()) {
@@ -8,7 +8,7 @@ fun f(): Unit {
<!DEBUG_INFO_CONSTANT!>x<!> <!UNSAFE_INFIX_CALL!>+<!> 1
<!DEBUG_INFO_CONSTANT!>x<!> <!UNSAFE_INFIX_CALL, INFIX_MODIFIER_REQUIRED!>plus<!> 1
<!DEBUG_INFO_CONSTANT!>x<!> <!UNSAFE_INFIX_CALL!><<!> 1
x <!UNSAFE_INFIX_CALL!>+=<!> 1
<!DEBUG_INFO_CONSTANT!>x<!> <!UNSAFE_INFIX_CALL!>+=<!> 1
<!DEBUG_INFO_CONSTANT!>x<!> == 1
<!DEBUG_INFO_CONSTANT!>x<!> != 1
+1 -1
View File
@@ -146,7 +146,7 @@ fun test() {
while (out2 == null) {
<!DEBUG_INFO_CONSTANT!>out2<!>?.println();
<!ALWAYS_NULL!>out2<!><!UNSAFE_CALL!>.<!>println();
<!DEBUG_INFO_CONSTANT!>out2<!><!UNSAFE_CALL!>.<!>println();
}
<!DEBUG_INFO_SMARTCAST!>out2<!>.println()
@@ -2,12 +2,12 @@ fun foo(): String {
var s: String?
s = null
<!DEBUG_INFO_CONSTANT!>s<!>?.length
<!ALWAYS_NULL!>s<!><!UNSAFE_CALL!>.<!>length
<!DEBUG_INFO_CONSTANT!>s<!><!UNSAFE_CALL!>.<!>length
if (<!SENSELESS_COMPARISON!><!DEBUG_INFO_CONSTANT!>s<!> == null<!>) return <!ALWAYS_NULL!>s<!>!!
var t: String? = "y"
if (t == null) t = "x"
var x: Int? = null
if (x == null) <!TYPE_MISMATCH!>x += null<!>
if (x == null) <!TYPE_MISMATCH!><!DEBUG_INFO_CONSTANT!>x<!> += null<!>
return <!DEBUG_INFO_SMARTCAST!>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
<!ALWAYS_NULL!>s<!>.gav()
<!DEBUG_INFO_CONSTANT!>s<!>.gav()
<!DEBUG_INFO_CONSTANT!>s<!> as? String
<!DEBUG_INFO_CONSTANT!>s<!> <!USELESS_CAST!>as String?<!>
<!ALWAYS_NULL!>s<!> as String
@@ -10,7 +10,7 @@ public class My {
fun test() {
val my = My.create()
if (my == null) {
<!ALWAYS_NULL!>my<!>.foo()
<!DEBUG_INFO_CONSTANT!>my<!><!UNSAFE_CALL!>.<!>foo()
}
}
@@ -15,12 +15,12 @@ fun foo(x : String?, y : String?) {
else {
// y == null but x != y
<!DEBUG_INFO_SMARTCAST!>x<!>.length
<!ALWAYS_NULL!>y<!><!UNSAFE_CALL!>.<!>length
<!DEBUG_INFO_CONSTANT!>y<!><!UNSAFE_CALL!>.<!>length
}
if (y == null && x != <!DEBUG_INFO_CONSTANT!>y<!>) {
// y == null but x != y
<!DEBUG_INFO_SMARTCAST!>x<!>.length
<!ALWAYS_NULL!>y<!><!UNSAFE_CALL!>.<!>length
<!DEBUG_INFO_CONSTANT!>y<!><!UNSAFE_CALL!>.<!>length
}
else {
x<!UNSAFE_CALL!>.<!>length
@@ -6,7 +6,7 @@ fun foo(x: String?, y: String?, z: String?, w: String?) {
if (x != null || y != null || (<!DEBUG_INFO_CONSTANT!>x<!> != z && <!DEBUG_INFO_CONSTANT!>y<!> != z))
z<!UNSAFE_CALL!>.<!>length
else
<!ALWAYS_NULL!>z<!><!UNSAFE_CALL!>.<!>length
<!DEBUG_INFO_CONSTANT!>z<!><!UNSAFE_CALL!>.<!>length
if (x == null || y == null || (x != z && y != z))
z<!UNSAFE_CALL!>.<!>length
else
@@ -4,7 +4,7 @@ fun foo() {
v = "abc"
<!DEBUG_INFO_SMARTCAST!>v<!>.length
v = null
<!ALWAYS_NULL!>v<!><!UNSAFE_CALL!>.<!>length
<!DEBUG_INFO_CONSTANT!>v<!><!UNSAFE_CALL!>.<!>length
v = "abc"
<!DEBUG_INFO_SMARTCAST!>v<!>.length
}
@@ -3,5 +3,5 @@ fun foo() {
// It is possible in principle to provide smart cast here
v<!UNSAFE_CALL!>.<!>length
v = null
<!ALWAYS_NULL!>v<!><!UNSAFE_CALL!>.<!>length
<!DEBUG_INFO_CONSTANT!>v<!><!UNSAFE_CALL!>.<!>length
}
@@ -1,5 +1,5 @@
fun foo(): Int {
var s: String? = <!VARIABLE_WITH_REDUNDANT_INITIALIZER!>"abc"<!>
s = null
return <!ALWAYS_NULL!>s<!><!UNSAFE_CALL!>.<!>length
return <!DEBUG_INFO_CONSTANT!>s<!><!UNSAFE_CALL!>.<!>length
}
@@ -9,5 +9,5 @@ fun test() {
fun <T> dynamic(body: dynamic.() -> T): T {
val topLevel = null
return <!ALWAYS_NULL!>topLevel<!>.body()
return topLevel.body()
}