Data flow values: initializers for local variables are now stored as "bound values" with inherited nullability #KT-6840 Fixed

This commit is contained in:
Mikhail Glukhikh
2016-07-25 12:03:55 +03:00
parent 7090abddcd
commit 4f7d8e34b0
9 changed files with 74 additions and 26 deletions
@@ -32,6 +32,7 @@ import org.jetbrains.kotlin.psi.*;
import org.jetbrains.kotlin.resolve.calls.inference.ConstraintSystemCompleter;
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.constants.CompileTimeConstant;
import org.jetbrains.kotlin.resolve.diagnostics.Diagnostics;
import org.jetbrains.kotlin.resolve.scopes.LexicalScope;
@@ -98,6 +99,7 @@ public interface BindingContext {
WritableSlice<KtExpression, KotlinType> EXPECTED_EXPRESSION_TYPE = new BasicWritableSlice<KtExpression, KotlinType>(DO_NOTHING);
WritableSlice<KtFunction, KotlinType> EXPECTED_RETURN_TYPE = new BasicWritableSlice<KtFunction, KotlinType>(DO_NOTHING);
WritableSlice<KtExpression, DataFlowInfo> DATAFLOW_INFO_AFTER_CONDITION = Slices.createSimpleSlice();
WritableSlice<VariableDescriptor, DataFlowValue> BOUND_INITIALIZER_VALUE = Slices.createSimpleSlice();
WritableSlice<KtExpression, LeakingThisDescriptor> LEAKING_THIS = Slices.createSimpleSlice();
/**
@@ -93,17 +93,22 @@ class LocalVariableResolver(
typeInfo = facade.getTypeInfo(initializer, context.replaceExpectedType(outType))
val dataFlowInfo = typeInfo.dataFlowInfo
val type = typeInfo.type
// At this moment we do not take initializer value into account if type is given for a property
// We can comment first part of this condition to take them into account, like here: var s: String? = "xyz"
// In this case s will be not-nullable until it is changed
if (property.typeReference == null && type != null) {
val variableDataFlowValue = DataFlowValueFactory.createDataFlowValueForProperty(
property, propertyDescriptor, context.trace.bindingContext,
DescriptorUtils.getContainingModuleOrNull(scope.ownerDescriptor))
if (type != null) {
val initializerDataFlowValue = DataFlowValueFactory.createDataFlowValue(initializer, type, context)
// We cannot say here anything new about initializerDataFlowValue
// except it has the same value as variableDataFlowValue
typeInfo = typeInfo.replaceDataFlowInfo(dataFlowInfo.assign(variableDataFlowValue, initializerDataFlowValue))
if (!propertyDescriptor.isVar) {
context.trace.record(BindingContext.BOUND_INITIALIZER_VALUE, propertyDescriptor, initializerDataFlowValue)
}
// At this moment we do not take initializer value into account if type is given for a property
// We can comment this condition to take them into account, like here: var s: String? = "xyz"
// In this case s will be not-nullable until it is changed
if (property.typeReference == null) {
val variableDataFlowValue = DataFlowValueFactory.createDataFlowValueForProperty(
property, propertyDescriptor, context.trace.bindingContext,
DescriptorUtils.getContainingModuleOrNull(scope.ownerDescriptor))
// We cannot say here anything new about initializerDataFlowValue
// except it has the same value as variableDataFlowValue
typeInfo = typeInfo.replaceDataFlowInfo(dataFlowInfo.assign(variableDataFlowValue, initializerDataFlowValue))
}
}
}
else {
@@ -27,8 +27,8 @@ import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.before
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.BindingContext.DECLARATION_TO_DESCRIPTOR
import org.jetbrains.kotlin.resolve.BindingContext.REFERENCE_TARGET
import org.jetbrains.kotlin.resolve.BindingContext.*
import org.jetbrains.kotlin.resolve.BindingTrace
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
import org.jetbrains.kotlin.resolve.calls.callUtil.isSafeCall
@@ -119,8 +119,10 @@ object DataFlowValueFactory {
bindingContext: BindingContext,
usageContainingModule: ModuleDescriptor?
) = DataFlowValue(IdentifierInfo.Variable(variableDescriptor,
variableKind(variableDescriptor, usageContainingModule, bindingContext, property)),
variableDescriptor.type)
variableKind(variableDescriptor, usageContainingModule,
bindingContext, property),
bindingContext[BOUND_INITIALIZER_VALUE, variableDescriptor]),
variableDescriptor.type)
// For only ++ and -- postfix operations
private data class PostfixIdentifierInfo(val argumentInfo: IdentifierInfo, val op: KtToken) : IdentifierInfo {
@@ -201,7 +203,8 @@ object DataFlowValueFactory {
val usageModuleDescriptor = DescriptorUtils.getContainingModuleOrNull(containingDeclarationOrModule)
val selectorInfo = IdentifierInfo.Variable(declarationDescriptor,
variableKind(declarationDescriptor, usageModuleDescriptor,
bindingContext, simpleNameExpression))
bindingContext, simpleNameExpression),
bindingContext[BOUND_INITIALIZER_VALUE, declarationDescriptor])
val implicitReceiver = resolvedCall?.dispatchReceiver
if (implicitReceiver == null) {
@@ -95,10 +95,17 @@ internal class DelegatingDataFlowInfo private constructor(
map.put(value, nullability)
val identifierInfo = value.identifierInfo
if (affectReceiver && !nullability.canBeNull() && identifierInfo is IdentifierInfo.Qualified) {
val receiverType = identifierInfo.receiverType
if (identifierInfo.safe && receiverType != null) {
putNullability(map, DataFlowValue(identifierInfo.receiverInfo, receiverType), nullability)
if (affectReceiver && !nullability.canBeNull()) {
when (identifierInfo) {
is IdentifierInfo.Qualified -> {
val receiverType = identifierInfo.receiverType
if (identifierInfo.safe && receiverType != null) {
putNullability(map, DataFlowValue(identifierInfo.receiverInfo, receiverType), nullability)
}
}
is IdentifierInfo.Variable -> identifierInfo.bound?.let {
putNullability(map, it, nullability)
}
}
}
@@ -39,7 +39,11 @@ interface IdentifierInfo {
override fun toString() = "ERROR"
}
class Variable(val variable: VariableDescriptor, override val kind: DataFlowValue.Kind) : IdentifierInfo {
class Variable(
val variable: VariableDescriptor,
override val kind: DataFlowValue.Kind,
val bound: DataFlowValue?
) : IdentifierInfo {
override fun equals(other: Any?) = other is Variable && variable == other.variable
@@ -5,8 +5,7 @@
fun kt6840_1(s: String?) {
val hash = s?.hashCode()
if (hash != null) {
// To be supported
s<!UNSAFE_CALL!>.<!>length
<!DEBUG_INFO_SMARTCAST!>s<!>.length
}
}
@@ -41,12 +40,11 @@ fun kt4565_1(a: SomeClass?) {
val data = a?.data
if (data != null) {
<!DEBUG_INFO_SMARTCAST!>data<!>.hashCode()
// To be supported
a<!UNSAFE_CALL!>.<!>hashCode()
a<!UNSAFE_CALL!>.<!>data.hashCode()
<!DEBUG_INFO_SMARTCAST!>a<!>.hashCode()
<!DEBUG_INFO_SMARTCAST!>a<!>.data.hashCode()
}
if (a?.data != null) {
// To be supported
// To be supported (?!)
data<!UNSAFE_CALL!>.<!>hashCode()
<!DEBUG_INFO_SMARTCAST!>a<!>.hashCode()
<!DEBUG_INFO_SMARTCAST!>a<!>.data.hashCode()
@@ -0,0 +1,20 @@
fun foo(arg: Int?) {
val x = arg
if (x != null) {
<!DEBUG_INFO_SMARTCAST!>arg<!>.hashCode()
}
val y: Any? = arg
if (y != null) {
<!DEBUG_INFO_SMARTCAST!>arg<!>.hashCode()
}
val yy: Any?
yy = arg
if (yy != null) {
arg<!UNSAFE_CALL!>.<!>hashCode()
}
var z = arg
z = z?.let { 42 }
if (z != null) {
arg<!UNSAFE_CALL!>.<!>hashCode()
}
}
@@ -0,0 +1,3 @@
package
public fun foo(/*0*/ arg: kotlin.Int?): kotlin.Unit
@@ -18620,6 +18620,12 @@ public class DiagnosticsTestGenerated extends AbstractDiagnosticsTest {
doTest(fileName);
}
@TestMetadata("boundInitializer.kt")
public void testBoundInitializer() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/varnotnull/boundInitializer.kt");
doTest(fileName);
}
@TestMetadata("doWhileWithBreak.kt")
public void testDoWhileWithBreak() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/varnotnull/doWhileWithBreak.kt");