From 4f7d8e34b07a410fe288e0edfaeab546f9c69ced Mon Sep 17 00:00:00 2001 From: Mikhail Glukhikh Date: Mon, 25 Jul 2016 12:03:55 +0300 Subject: [PATCH] Data flow values: initializers for local variables are now stored as "bound values" with inherited nullability #KT-6840 Fixed --- .../kotlin/resolve/BindingContext.java | 2 ++ .../kotlin/resolve/LocalVariableResolver.kt | 25 +++++++++++-------- .../calls/smartcasts/DataFlowValueFactory.kt | 13 ++++++---- .../smartcasts/DelegatingDataFlowInfo.kt | 15 ++++++++--- .../calls/smartcasts/IdentifierInfo.kt | 6 ++++- .../safecalls/safeAccessReceiverNotNull.kt | 10 +++----- .../smartCasts/varnotnull/boundInitializer.kt | 20 +++++++++++++++ .../varnotnull/boundInitializer.txt | 3 +++ .../checkers/DiagnosticsTestGenerated.java | 6 +++++ 9 files changed, 74 insertions(+), 26 deletions(-) create mode 100644 compiler/testData/diagnostics/tests/smartCasts/varnotnull/boundInitializer.kt create mode 100644 compiler/testData/diagnostics/tests/smartCasts/varnotnull/boundInitializer.txt diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/BindingContext.java b/compiler/frontend/src/org/jetbrains/kotlin/resolve/BindingContext.java index b70288184b2..3c4ef3338ce 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/BindingContext.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/BindingContext.java @@ -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 EXPECTED_EXPRESSION_TYPE = new BasicWritableSlice(DO_NOTHING); WritableSlice EXPECTED_RETURN_TYPE = new BasicWritableSlice(DO_NOTHING); WritableSlice DATAFLOW_INFO_AFTER_CONDITION = Slices.createSimpleSlice(); + WritableSlice BOUND_INITIALIZER_VALUE = Slices.createSimpleSlice(); WritableSlice LEAKING_THIS = Slices.createSimpleSlice(); /** diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/LocalVariableResolver.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/LocalVariableResolver.kt index c23299e54fc..8134f14bbb2 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/LocalVariableResolver.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/LocalVariableResolver.kt @@ -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 { diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/smartcasts/DataFlowValueFactory.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/smartcasts/DataFlowValueFactory.kt index d3b4c96f1d8..8944fb76091 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/smartcasts/DataFlowValueFactory.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/smartcasts/DataFlowValueFactory.kt @@ -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) { diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/smartcasts/DelegatingDataFlowInfo.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/smartcasts/DelegatingDataFlowInfo.kt index 80112dac344..4088d1e0178 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/smartcasts/DelegatingDataFlowInfo.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/smartcasts/DelegatingDataFlowInfo.kt @@ -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) + } } } diff --git a/compiler/resolution/src/org/jetbrains/kotlin/resolve/calls/smartcasts/IdentifierInfo.kt b/compiler/resolution/src/org/jetbrains/kotlin/resolve/calls/smartcasts/IdentifierInfo.kt index f6237162ae8..49098b4b1ad 100644 --- a/compiler/resolution/src/org/jetbrains/kotlin/resolve/calls/smartcasts/IdentifierInfo.kt +++ b/compiler/resolution/src/org/jetbrains/kotlin/resolve/calls/smartcasts/IdentifierInfo.kt @@ -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 diff --git a/compiler/testData/diagnostics/tests/smartCasts/safecalls/safeAccessReceiverNotNull.kt b/compiler/testData/diagnostics/tests/smartCasts/safecalls/safeAccessReceiverNotNull.kt index 463e4bedcac..d6b45586d18 100644 --- a/compiler/testData/diagnostics/tests/smartCasts/safecalls/safeAccessReceiverNotNull.kt +++ b/compiler/testData/diagnostics/tests/smartCasts/safecalls/safeAccessReceiverNotNull.kt @@ -5,8 +5,7 @@ fun kt6840_1(s: String?) { val hash = s?.hashCode() if (hash != null) { - // To be supported - s.length + s.length } } @@ -41,12 +40,11 @@ fun kt4565_1(a: SomeClass?) { val data = a?.data if (data != null) { data.hashCode() - // To be supported - a.hashCode() - a.data.hashCode() + a.hashCode() + a.data.hashCode() } if (a?.data != null) { - // To be supported + // To be supported (?!) data.hashCode() a.hashCode() a.data.hashCode() diff --git a/compiler/testData/diagnostics/tests/smartCasts/varnotnull/boundInitializer.kt b/compiler/testData/diagnostics/tests/smartCasts/varnotnull/boundInitializer.kt new file mode 100644 index 00000000000..1a93d1141f4 --- /dev/null +++ b/compiler/testData/diagnostics/tests/smartCasts/varnotnull/boundInitializer.kt @@ -0,0 +1,20 @@ +fun foo(arg: Int?) { + val x = arg + if (x != null) { + arg.hashCode() + } + val y: Any? = arg + if (y != null) { + arg.hashCode() + } + val yy: Any? + yy = arg + if (yy != null) { + arg.hashCode() + } + var z = arg + z = z?.let { 42 } + if (z != null) { + arg.hashCode() + } +} diff --git a/compiler/testData/diagnostics/tests/smartCasts/varnotnull/boundInitializer.txt b/compiler/testData/diagnostics/tests/smartCasts/varnotnull/boundInitializer.txt new file mode 100644 index 00000000000..419cb9b50f8 --- /dev/null +++ b/compiler/testData/diagnostics/tests/smartCasts/varnotnull/boundInitializer.txt @@ -0,0 +1,3 @@ +package + +public fun foo(/*0*/ arg: kotlin.Int?): kotlin.Unit diff --git a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java index 2da5ef43d2f..2bc7856df5e 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java @@ -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");