diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/smartcasts/DataFlowValueFactory.java b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/smartcasts/DataFlowValueFactory.java index 7866a9eab53..f8549552475 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/smartcasts/DataFlowValueFactory.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/smartcasts/DataFlowValueFactory.java @@ -78,6 +78,12 @@ public class DataFlowValueFactory { } } + @NotNull + public static DataFlowValue createDataFlowValue(@NotNull VariableDescriptor variableDescriptor) { + JetType type = variableDescriptor.getType(); + return new DataFlowValue(variableDescriptor, type, isStableVariable(variableDescriptor), getImmanentNullability(type)); + } + @NotNull private static Nullability getImmanentNullability(@NotNull JetType type) { return TypeUtils.isNullableType(type) ? Nullability.UNKNOWN : Nullability.NOT_NULL; diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/smartcasts/DelegatingDataFlowInfo.java b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/smartcasts/DelegatingDataFlowInfo.java index f631d7e6001..7fa20d8df2c 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/smartcasts/DelegatingDataFlowInfo.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/smartcasts/DelegatingDataFlowInfo.java @@ -22,6 +22,7 @@ import org.jetbrains.annotations.Nullable; import org.jetbrains.kotlin.types.JetType; import org.jetbrains.kotlin.types.TypeUtils; +import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; @@ -130,7 +131,39 @@ import static org.jetbrains.kotlin.resolve.calls.smartcasts.Nullability.NOT_NULL boolean changed = false; changed |= putNullability(builder, a, nullabilityOfA.refine(nullabilityOfB)); changed |= putNullability(builder, b, nullabilityOfB.refine(nullabilityOfA)); - return changed ? new DelegatingDataFlowInfo(this, ImmutableMap.copyOf(builder), EMPTY_TYPE_INFO) : this; + + SetMultimap newTypeInfo = newTypeInfo(); + newTypeInfo.putAll(a, collectTypesFromMeAndParents(b)); + newTypeInfo.putAll(b, collectTypesFromMeAndParents(a)); + changed |= !newTypeInfo.isEmpty(); + + return !changed + ? this + : new DelegatingDataFlowInfo( + this, + ImmutableMap.copyOf(builder), + newTypeInfo.isEmpty() ? EMPTY_TYPE_INFO : newTypeInfo + ); + } + + @NotNull + private Set collectTypesFromMeAndParents(@NotNull DataFlowValue value) { + Set types = new LinkedHashSet(); + + DataFlowInfo current = this; + while (current != null) { + if (current instanceof DelegatingDataFlowInfo) { + DelegatingDataFlowInfo delegatingInfo = (DelegatingDataFlowInfo) current; + types.addAll(delegatingInfo.typeInfo.get(value)); + current = delegatingInfo.parent; + } + else { + types.addAll(current.getPossibleTypes(value)); + break; + } + } + + return types; } @Override diff --git a/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/ExpressionTypingVisitorForStatements.java b/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/ExpressionTypingVisitorForStatements.java index cca21423485..0e8b750ebc6 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/ExpressionTypingVisitorForStatements.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/ExpressionTypingVisitorForStatements.java @@ -38,6 +38,8 @@ import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall; import org.jetbrains.kotlin.resolve.calls.results.OverloadResolutionResults; import org.jetbrains.kotlin.resolve.calls.results.OverloadResolutionResultsUtil; import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowInfo; +import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowValue; +import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowValueFactory; import org.jetbrains.kotlin.resolve.scopes.JetScope; import org.jetbrains.kotlin.resolve.scopes.WritableScope; import org.jetbrains.kotlin.resolve.scopes.receivers.ExpressionReceiver; @@ -135,6 +137,12 @@ public class ExpressionTypingVisitorForStatements extends ExpressionTypingVisito JetType outType = propertyDescriptor.getType(); JetTypeInfo typeInfo = facade.getTypeInfo(initializer, context.replaceExpectedType(outType)); dataFlowInfo = typeInfo.getDataFlowInfo(); + JetType type = typeInfo.getType(); + if (property.getTypeReference() == null && type != null) { + DataFlowValue variableDataFlowValue = DataFlowValueFactory.createDataFlowValue(propertyDescriptor); + DataFlowValue initializerDataFlowValue = DataFlowValueFactory.createDataFlowValue(initializer, type, context.trace.getBindingContext()); + dataFlowInfo = dataFlowInfo.equate(variableDataFlowValue, initializerDataFlowValue); + } } { @@ -335,6 +343,12 @@ public class ExpressionTypingVisitorForStatements extends ExpressionTypingVisito if (right != null) { JetTypeInfo rightInfo = facade.getTypeInfo(right, context.replaceDataFlowInfo(dataFlowInfo).replaceExpectedType(leftType)); dataFlowInfo = rightInfo.getDataFlowInfo(); + JetType rightType = rightInfo.getType(); + if (left != null && leftType != null && rightType != null) { + DataFlowValue leftValue = DataFlowValueFactory.createDataFlowValue(left, leftType, context.trace.getBindingContext()); + DataFlowValue rightValue = DataFlowValueFactory.createDataFlowValue(right, rightType, context.trace.getBindingContext()); + dataFlowInfo = dataFlowInfo.equate(leftValue, rightValue); + } } if (leftType != null && leftOperand != null) { //if leftType == null, some other error has been generated BasicExpressionTypingVisitor.checkLValue(context.trace, leftOperand); diff --git a/compiler/testData/diagnostics/tests/dataFlow/assignment/assignToNewVal.kt b/compiler/testData/diagnostics/tests/dataFlow/assignment/assignToNewVal.kt new file mode 100644 index 00000000000..2c5872c4c18 --- /dev/null +++ b/compiler/testData/diagnostics/tests/dataFlow/assignment/assignToNewVal.kt @@ -0,0 +1,10 @@ +fun test(a: Any?) { + if (a == null) return + a.hashCode() + + val b = a + b.hashCode() + + val c: Any? = a + c.hashCode() +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/dataFlow/assignment/assignToNewVal.txt b/compiler/testData/diagnostics/tests/dataFlow/assignment/assignToNewVal.txt new file mode 100644 index 00000000000..4f4fc5b5bf5 --- /dev/null +++ b/compiler/testData/diagnostics/tests/dataFlow/assignment/assignToNewVal.txt @@ -0,0 +1,3 @@ +package + +internal fun test(/*0*/ a: kotlin.Any?): kotlin.Unit diff --git a/compiler/testData/diagnostics/tests/dataFlow/assignment/kt6118.kt b/compiler/testData/diagnostics/tests/dataFlow/assignment/kt6118.kt new file mode 100644 index 00000000000..d281770dbfc --- /dev/null +++ b/compiler/testData/diagnostics/tests/dataFlow/assignment/kt6118.kt @@ -0,0 +1,16 @@ +// KT-6118 Redundant type cast can be not redundant? + +fun foo(o: Any) { + if (o is String) { + val s = o as String + s.length() + } +} + +fun foo1(o: Any) { + if (o is String) { + o.length() + val s = o + s.length() + } +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/dataFlow/assignment/kt6118.txt b/compiler/testData/diagnostics/tests/dataFlow/assignment/kt6118.txt new file mode 100644 index 00000000000..b02f2a543cf --- /dev/null +++ b/compiler/testData/diagnostics/tests/dataFlow/assignment/kt6118.txt @@ -0,0 +1,4 @@ +package + +internal fun foo(/*0*/ o: kotlin.Any): kotlin.Unit +internal fun foo1(/*0*/ o: kotlin.Any): kotlin.Unit diff --git a/compiler/testData/diagnostics/tests/dataFlow/assignment/uninitializedValIsCheck.kt b/compiler/testData/diagnostics/tests/dataFlow/assignment/uninitializedValIsCheck.kt new file mode 100644 index 00000000000..eba5c0adf39 --- /dev/null +++ b/compiler/testData/diagnostics/tests/dataFlow/assignment/uninitializedValIsCheck.kt @@ -0,0 +1,15 @@ +fun test(a: Any?, flag: Boolean, x: Any?) { + if (a !is String) return + a.length() + + val b: Any? + + if (flag) { + b = a + b.length() + } + else { + b = x + b.length() + } +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/dataFlow/assignment/uninitializedValIsCheck.txt b/compiler/testData/diagnostics/tests/dataFlow/assignment/uninitializedValIsCheck.txt new file mode 100644 index 00000000000..66a6e254d99 --- /dev/null +++ b/compiler/testData/diagnostics/tests/dataFlow/assignment/uninitializedValIsCheck.txt @@ -0,0 +1,3 @@ +package + +internal fun test(/*0*/ a: kotlin.Any?, /*1*/ flag: kotlin.Boolean, /*2*/ x: kotlin.Any?): kotlin.Unit diff --git a/compiler/testData/diagnostics/tests/dataFlow/assignment/uninitializedValNullability.kt b/compiler/testData/diagnostics/tests/dataFlow/assignment/uninitializedValNullability.kt new file mode 100644 index 00000000000..4e0ee6e9792 --- /dev/null +++ b/compiler/testData/diagnostics/tests/dataFlow/assignment/uninitializedValNullability.kt @@ -0,0 +1,15 @@ +fun test(a: Any?, flag: Boolean, x: Any?) { + if (a == null) return + a.hashCode() + + val b: Any? + + if (flag) { + b = a + b.hashCode() + } + else { + b = x + b.hashCode() + } +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/dataFlow/assignment/uninitializedValNullability.txt b/compiler/testData/diagnostics/tests/dataFlow/assignment/uninitializedValNullability.txt new file mode 100644 index 00000000000..66a6e254d99 --- /dev/null +++ b/compiler/testData/diagnostics/tests/dataFlow/assignment/uninitializedValNullability.txt @@ -0,0 +1,3 @@ +package + +internal fun test(/*0*/ a: kotlin.Any?, /*1*/ flag: kotlin.Boolean, /*2*/ x: kotlin.Any?): kotlin.Unit diff --git a/compiler/testData/diagnostics/tests/dataFlow/assignment/when.kt b/compiler/testData/diagnostics/tests/dataFlow/assignment/when.kt new file mode 100644 index 00000000000..8abde6098a9 --- /dev/null +++ b/compiler/testData/diagnostics/tests/dataFlow/assignment/when.kt @@ -0,0 +1,12 @@ +fun test(a: Any?) { + when (a) { + is String -> { + val s = a + s.length() + } + "" -> { + val s = a + s.hashCode() + } + } +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/dataFlow/assignment/when.txt b/compiler/testData/diagnostics/tests/dataFlow/assignment/when.txt new file mode 100644 index 00000000000..4f4fc5b5bf5 --- /dev/null +++ b/compiler/testData/diagnostics/tests/dataFlow/assignment/when.txt @@ -0,0 +1,3 @@ +package + +internal fun test(/*0*/ a: kotlin.Any?): kotlin.Unit diff --git a/compiler/tests/org/jetbrains/kotlin/checkers/JetDiagnosticsTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/JetDiagnosticsTestGenerated.java index 5cccf442aed..59c6552b0ed 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/JetDiagnosticsTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/JetDiagnosticsTestGenerated.java @@ -2277,7 +2277,7 @@ public class JetDiagnosticsTestGenerated extends AbstractJetDiagnosticsTest { @TestMetadata("compiler/testData/diagnostics/tests/dataFlow") @TestDataPath("$PROJECT_ROOT") - @InnerTestClasses({DataFlow.Local.class}) + @InnerTestClasses({DataFlow.Assignment.class, DataFlow.Local.class}) @RunWith(JUnit3RunnerWithInners.class) public static class DataFlow extends AbstractJetDiagnosticsTest { public void testAllFilesPresentInDataFlow() throws Exception { @@ -2308,6 +2308,45 @@ public class JetDiagnosticsTestGenerated extends AbstractJetDiagnosticsTest { doTest(fileName); } + @TestMetadata("compiler/testData/diagnostics/tests/dataFlow/assignment") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class Assignment extends AbstractJetDiagnosticsTest { + public void testAllFilesPresentInAssignment() throws Exception { + JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/diagnostics/tests/dataFlow/assignment"), Pattern.compile("^(.+)\\.kt$"), true); + } + + @TestMetadata("assignToNewVal.kt") + public void testAssignToNewVal() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/dataFlow/assignment/assignToNewVal.kt"); + doTest(fileName); + } + + @TestMetadata("kt6118.kt") + public void testKt6118() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/dataFlow/assignment/kt6118.kt"); + doTest(fileName); + } + + @TestMetadata("uninitializedValIsCheck.kt") + public void testUninitializedValIsCheck() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/dataFlow/assignment/uninitializedValIsCheck.kt"); + doTest(fileName); + } + + @TestMetadata("uninitializedValNullability.kt") + public void testUninitializedValNullability() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/dataFlow/assignment/uninitializedValNullability.kt"); + doTest(fileName); + } + + @TestMetadata("when.kt") + public void testWhen() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/dataFlow/assignment/when.kt"); + doTest(fileName); + } + } + @TestMetadata("compiler/testData/diagnostics/tests/dataFlow/local") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class)