Data flow values: initializers for local variables are now stored as "bound values" with inherited nullability #KT-6840 Fixed
This commit is contained in:
@@ -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 {
|
||||
|
||||
+8
-5
@@ -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) {
|
||||
|
||||
+11
-4
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+5
-1
@@ -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
|
||||
|
||||
|
||||
+4
-6
@@ -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()
|
||||
|
||||
+20
@@ -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()
|
||||
}
|
||||
}
|
||||
+3
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user