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.inference.ConstraintSystemCompleter;
|
||||||
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall;
|
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall;
|
||||||
import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowInfo;
|
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.constants.CompileTimeConstant;
|
||||||
import org.jetbrains.kotlin.resolve.diagnostics.Diagnostics;
|
import org.jetbrains.kotlin.resolve.diagnostics.Diagnostics;
|
||||||
import org.jetbrains.kotlin.resolve.scopes.LexicalScope;
|
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<KtExpression, KotlinType> EXPECTED_EXPRESSION_TYPE = new BasicWritableSlice<KtExpression, KotlinType>(DO_NOTHING);
|
||||||
WritableSlice<KtFunction, KotlinType> EXPECTED_RETURN_TYPE = new BasicWritableSlice<KtFunction, 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<KtExpression, DataFlowInfo> DATAFLOW_INFO_AFTER_CONDITION = Slices.createSimpleSlice();
|
||||||
|
WritableSlice<VariableDescriptor, DataFlowValue> BOUND_INITIALIZER_VALUE = Slices.createSimpleSlice();
|
||||||
WritableSlice<KtExpression, LeakingThisDescriptor> LEAKING_THIS = Slices.createSimpleSlice();
|
WritableSlice<KtExpression, LeakingThisDescriptor> LEAKING_THIS = Slices.createSimpleSlice();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -93,17 +93,22 @@ class LocalVariableResolver(
|
|||||||
typeInfo = facade.getTypeInfo(initializer, context.replaceExpectedType(outType))
|
typeInfo = facade.getTypeInfo(initializer, context.replaceExpectedType(outType))
|
||||||
val dataFlowInfo = typeInfo.dataFlowInfo
|
val dataFlowInfo = typeInfo.dataFlowInfo
|
||||||
val type = typeInfo.type
|
val type = typeInfo.type
|
||||||
// At this moment we do not take initializer value into account if type is given for a property
|
if (type != null) {
|
||||||
// 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))
|
|
||||||
val initializerDataFlowValue = DataFlowValueFactory.createDataFlowValue(initializer, type, context)
|
val initializerDataFlowValue = DataFlowValueFactory.createDataFlowValue(initializer, type, context)
|
||||||
// We cannot say here anything new about initializerDataFlowValue
|
if (!propertyDescriptor.isVar) {
|
||||||
// except it has the same value as variableDataFlowValue
|
context.trace.record(BindingContext.BOUND_INITIALIZER_VALUE, propertyDescriptor, initializerDataFlowValue)
|
||||||
typeInfo = typeInfo.replaceDataFlowInfo(dataFlowInfo.assign(variableDataFlowValue, 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 {
|
else {
|
||||||
|
|||||||
+8
-5
@@ -27,8 +27,8 @@ import org.jetbrains.kotlin.lexer.KtTokens
|
|||||||
import org.jetbrains.kotlin.psi.*
|
import org.jetbrains.kotlin.psi.*
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.before
|
import org.jetbrains.kotlin.psi.psiUtil.before
|
||||||
import org.jetbrains.kotlin.resolve.BindingContext
|
import org.jetbrains.kotlin.resolve.BindingContext
|
||||||
import org.jetbrains.kotlin.resolve.BindingContext.DECLARATION_TO_DESCRIPTOR
|
import org.jetbrains.kotlin.resolve.BindingContext.*
|
||||||
import org.jetbrains.kotlin.resolve.BindingContext.REFERENCE_TARGET
|
import org.jetbrains.kotlin.resolve.BindingTrace
|
||||||
import org.jetbrains.kotlin.resolve.DescriptorUtils
|
import org.jetbrains.kotlin.resolve.DescriptorUtils
|
||||||
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
|
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
|
||||||
import org.jetbrains.kotlin.resolve.calls.callUtil.isSafeCall
|
import org.jetbrains.kotlin.resolve.calls.callUtil.isSafeCall
|
||||||
@@ -119,8 +119,10 @@ object DataFlowValueFactory {
|
|||||||
bindingContext: BindingContext,
|
bindingContext: BindingContext,
|
||||||
usageContainingModule: ModuleDescriptor?
|
usageContainingModule: ModuleDescriptor?
|
||||||
) = DataFlowValue(IdentifierInfo.Variable(variableDescriptor,
|
) = DataFlowValue(IdentifierInfo.Variable(variableDescriptor,
|
||||||
variableKind(variableDescriptor, usageContainingModule, bindingContext, property)),
|
variableKind(variableDescriptor, usageContainingModule,
|
||||||
variableDescriptor.type)
|
bindingContext, property),
|
||||||
|
bindingContext[BOUND_INITIALIZER_VALUE, variableDescriptor]),
|
||||||
|
variableDescriptor.type)
|
||||||
|
|
||||||
// For only ++ and -- postfix operations
|
// For only ++ and -- postfix operations
|
||||||
private data class PostfixIdentifierInfo(val argumentInfo: IdentifierInfo, val op: KtToken) : IdentifierInfo {
|
private data class PostfixIdentifierInfo(val argumentInfo: IdentifierInfo, val op: KtToken) : IdentifierInfo {
|
||||||
@@ -201,7 +203,8 @@ object DataFlowValueFactory {
|
|||||||
val usageModuleDescriptor = DescriptorUtils.getContainingModuleOrNull(containingDeclarationOrModule)
|
val usageModuleDescriptor = DescriptorUtils.getContainingModuleOrNull(containingDeclarationOrModule)
|
||||||
val selectorInfo = IdentifierInfo.Variable(declarationDescriptor,
|
val selectorInfo = IdentifierInfo.Variable(declarationDescriptor,
|
||||||
variableKind(declarationDescriptor, usageModuleDescriptor,
|
variableKind(declarationDescriptor, usageModuleDescriptor,
|
||||||
bindingContext, simpleNameExpression))
|
bindingContext, simpleNameExpression),
|
||||||
|
bindingContext[BOUND_INITIALIZER_VALUE, declarationDescriptor])
|
||||||
|
|
||||||
val implicitReceiver = resolvedCall?.dispatchReceiver
|
val implicitReceiver = resolvedCall?.dispatchReceiver
|
||||||
if (implicitReceiver == null) {
|
if (implicitReceiver == null) {
|
||||||
|
|||||||
+11
-4
@@ -95,10 +95,17 @@ internal class DelegatingDataFlowInfo private constructor(
|
|||||||
map.put(value, nullability)
|
map.put(value, nullability)
|
||||||
|
|
||||||
val identifierInfo = value.identifierInfo
|
val identifierInfo = value.identifierInfo
|
||||||
if (affectReceiver && !nullability.canBeNull() && identifierInfo is IdentifierInfo.Qualified) {
|
if (affectReceiver && !nullability.canBeNull()) {
|
||||||
val receiverType = identifierInfo.receiverType
|
when (identifierInfo) {
|
||||||
if (identifierInfo.safe && receiverType != null) {
|
is IdentifierInfo.Qualified -> {
|
||||||
putNullability(map, DataFlowValue(identifierInfo.receiverInfo, receiverType), nullability)
|
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"
|
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
|
override fun equals(other: Any?) = other is Variable && variable == other.variable
|
||||||
|
|
||||||
|
|||||||
+4
-6
@@ -5,8 +5,7 @@
|
|||||||
fun kt6840_1(s: String?) {
|
fun kt6840_1(s: String?) {
|
||||||
val hash = s?.hashCode()
|
val hash = s?.hashCode()
|
||||||
if (hash != null) {
|
if (hash != null) {
|
||||||
// To be supported
|
<!DEBUG_INFO_SMARTCAST!>s<!>.length
|
||||||
s<!UNSAFE_CALL!>.<!>length
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,12 +40,11 @@ fun kt4565_1(a: SomeClass?) {
|
|||||||
val data = a?.data
|
val data = a?.data
|
||||||
if (data != null) {
|
if (data != null) {
|
||||||
<!DEBUG_INFO_SMARTCAST!>data<!>.hashCode()
|
<!DEBUG_INFO_SMARTCAST!>data<!>.hashCode()
|
||||||
// To be supported
|
<!DEBUG_INFO_SMARTCAST!>a<!>.hashCode()
|
||||||
a<!UNSAFE_CALL!>.<!>hashCode()
|
<!DEBUG_INFO_SMARTCAST!>a<!>.data.hashCode()
|
||||||
a<!UNSAFE_CALL!>.<!>data.hashCode()
|
|
||||||
}
|
}
|
||||||
if (a?.data != null) {
|
if (a?.data != null) {
|
||||||
// To be supported
|
// To be supported (?!)
|
||||||
data<!UNSAFE_CALL!>.<!>hashCode()
|
data<!UNSAFE_CALL!>.<!>hashCode()
|
||||||
<!DEBUG_INFO_SMARTCAST!>a<!>.hashCode()
|
<!DEBUG_INFO_SMARTCAST!>a<!>.hashCode()
|
||||||
<!DEBUG_INFO_SMARTCAST!>a<!>.data.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);
|
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")
|
@TestMetadata("doWhileWithBreak.kt")
|
||||||
public void testDoWhileWithBreak() throws Exception {
|
public void testDoWhileWithBreak() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/varnotnull/doWhileWithBreak.kt");
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/varnotnull/doWhileWithBreak.kt");
|
||||||
|
|||||||
Reference in New Issue
Block a user