diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/CandidateResolver.java b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/CandidateResolver.java index 5bb0f18315d..0ee30300b9f 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/CandidateResolver.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/CandidateResolver.java @@ -446,7 +446,7 @@ public class CandidateResolver { if (deparenthesizedArgument == null || type == null) return type; DataFlowValue dataFlowValue = DataFlowValueFactory.createDataFlowValue(deparenthesizedArgument, type, context); - if (!dataFlowValue.isStableIdentifier() && !dataFlowValue.isLocalVariable()) return type; + if (!dataFlowValue.isPredictable()) return type; Set possibleTypes = context.dataFlowInfo.getPossibleTypes(dataFlowValue); if (possibleTypes.isEmpty()) return type; diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/smartcasts/DataFlowValue.java b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/smartcasts/DataFlowValue.java index b3d2e943657..b58edb95a48 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/smartcasts/DataFlowValue.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/smartcasts/DataFlowValue.java @@ -33,15 +33,17 @@ public class DataFlowValue { public static final DataFlowValue ERROR = new DataFlowValue(new Object(), ErrorUtils.createErrorType("Error type for data flow"), false, false, Nullability.IMPOSSIBLE); private final boolean stableIdentifier; - private final boolean localVariable; + private final boolean uncapturedlocalVariable; private final JetType type; private final Object id; private final Nullability immanentNullability; // Use DataFlowValueFactory - /*package*/ DataFlowValue(Object id, JetType type, boolean stableIdentifier, boolean localVariable, Nullability immanentNullability) { + /*package*/ DataFlowValue(Object id, JetType type, boolean stableIdentifier, boolean uncapturedlocalVariable, Nullability immanentNullability) { + assert !stableIdentifier || !uncapturedlocalVariable : + "data flow value cannot be together a stable identifier and an uncaptured local variable"; this.stableIdentifier = stableIdentifier; - this.localVariable = localVariable; + this.uncapturedlocalVariable = uncapturedlocalVariable; this.type = type; this.id = id; this.immanentNullability = immanentNullability; @@ -59,16 +61,28 @@ public class DataFlowValue { /** * Stable identifier is a non-literal value that is statically known to be immutable + * + * NB: this function is no longer public! + * If you are checking for a possible smart cast, probably you need isPredictable() instead */ - public boolean isStableIdentifier() { + private boolean isStableIdentifier() { return stableIdentifier; } /** * Identifier is considered a local variable here if it's mutable (var), local and not captured in a closure */ - public boolean isLocalVariable() { - return localVariable; + public boolean isUncapturedLocalVariable() { + return uncapturedlocalVariable; + } + + /** + * Both stable identifiers and uncaptured local variables are considered "predictable". + * Predictable means here we do not expect some sudden change of their values, + * like accessing mutable properties in another thread or mutable variables from closures. + */ + public boolean isPredictable() { + return stableIdentifier || uncapturedlocalVariable; } @NotNull 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 f594536ee0c..3fa0f73f2c2 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 @@ -130,7 +130,7 @@ public class DataFlowValueFactory { JetType type = variableDescriptor.getType(); return new DataFlowValue(variableDescriptor, type, isStableVariable(variableDescriptor, usageContainingModule), - isLocalVariable(variableDescriptor, bindingContext), + isUncapturedLocalVariable(variableDescriptor, bindingContext), getImmanentNullability(type)); } @@ -165,6 +165,11 @@ public class DataFlowValueFactory { return new IdentifierInfo(id, isStable, isLocal, false); } + @NotNull + private static IdentifierInfo createStableInfo(Object id) { + return createInfo(id, true, false); + } + @NotNull private static IdentifierInfo createPackageInfo(Object id) { return new IdentifierInfo(id, true, false, true); @@ -180,6 +185,7 @@ public class DataFlowValueFactory { } return createInfo(Pair.create(receiverInfo.id, selectorInfo.id), receiverInfo.isStable && selectorInfo.isStable, + // x.y can never be a local variable false); } @@ -240,7 +246,7 @@ public class DataFlowValueFactory { VariableDescriptor variableDescriptor = (VariableDescriptor) declarationDescriptor; return combineInfo(receiverInfo, createInfo(variableDescriptor, isStableVariable(variableDescriptor, usageModuleDescriptor), - isLocalVariable(variableDescriptor, bindingContext))); + isUncapturedLocalVariable(variableDescriptor, bindingContext))); } if (declarationDescriptor instanceof PackageViewDescriptor) { return createPackageInfo(declarationDescriptor); @@ -267,21 +273,18 @@ public class DataFlowValueFactory { ReceiverParameterDescriptor receiverParameter = ((CallableDescriptor) descriptorOfThisReceiver).getExtensionReceiverParameter(); assert receiverParameter != null : "'This' refers to the callable member without a receiver parameter: " + descriptorOfThisReceiver; - return createInfo(receiverParameter.getValue(), true, false); + return createStableInfo(receiverParameter.getValue()); } if (descriptorOfThisReceiver instanceof ClassDescriptor) { - return createInfo(((ClassDescriptor) descriptorOfThisReceiver).getThisAsReceiverParameter().getValue(), true, false); + return createStableInfo(((ClassDescriptor) descriptorOfThisReceiver).getThisAsReceiverParameter().getValue()); } return NO_IDENTIFIER_INFO; } - public static boolean isLocalVariable(@NotNull VariableDescriptor variableDescriptor, @NotNull BindingContext bindingContext) { - if (variableDescriptor.isVar() && variableDescriptor instanceof LocalVariableDescriptor) { - if (BindingContextUtils.isVarCapturedInClosure(bindingContext, variableDescriptor)) - return false; - return true; - } - return false; + public static boolean isUncapturedLocalVariable(@NotNull VariableDescriptor variableDescriptor, @NotNull BindingContext bindingContext) { + return variableDescriptor.isVar() + && variableDescriptor instanceof LocalVariableDescriptor + && !BindingContextUtils.isVarCapturedInClosure(bindingContext, variableDescriptor); } /** 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 bd617b7fb8c..74327cce9a3 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 @@ -39,14 +39,12 @@ import static org.jetbrains.kotlin.resolve.calls.smartcasts.Nullability.NOT_NULL @NotNull private final ImmutableMap nullabilityInfo; - /** - * Also immutable - */ + // Also immutable @NotNull private final SetMultimap typeInfo; /** - * Value for which type info was cleared at this point + * Value for which type info was cleared or reassigned at this point * so parent type info should not be in use */ @Nullable @@ -94,16 +92,16 @@ import static org.jetbrains.kotlin.resolve.calls.smartcasts.Nullability.NOT_NULL @NotNull public SetMultimap getCompleteTypeInfo() { SetMultimap result = newTypeInfo(); - Set resultCompleted = new HashSet(); + Set withGivenTypeInfo = new HashSet(); DelegatingDataFlowInfo info = this; while (info != null) { for (DataFlowValue key : info.typeInfo.keySet()) { - if (!resultCompleted.contains(key)) { + if (!withGivenTypeInfo.contains(key)) { result.putAll(key, info.typeInfo.get(key)); } } if (valueWithGivenTypeInfo != null) { - resultCompleted.add(valueWithGivenTypeInfo); + withGivenTypeInfo.add(valueWithGivenTypeInfo); } info = (DelegatingDataFlowInfo) info.parent; } @@ -113,7 +111,7 @@ import static org.jetbrains.kotlin.resolve.calls.smartcasts.Nullability.NOT_NULL @Override @NotNull public Nullability getNullability(@NotNull DataFlowValue key) { - if (!key.isStableIdentifier() && !key.isLocalVariable()) return key.getImmanentNullability(); + if (!key.isPredictable()) return key.getImmanentNullability(); Nullability nullability = nullabilityInfo.get(key); return nullability != null ? nullability : parent != null ? parent.getNullability(key) : @@ -125,7 +123,7 @@ import static org.jetbrains.kotlin.resolve.calls.smartcasts.Nullability.NOT_NULL @NotNull DataFlowValue value, @NotNull Nullability nullability ) { - if (!value.isStableIdentifier() && !value.isLocalVariable()) return false; + if (!value.isPredictable()) return false; map.put(value, nullability); return nullability != getNullability(value); } @@ -160,14 +158,8 @@ import static org.jetbrains.kotlin.resolve.calls.smartcasts.Nullability.NOT_NULL @NotNull public DataFlowInfo clearValueInfo(@NotNull DataFlowValue value) { Map builder = Maps.newHashMap(); - boolean changed = putNullability(builder, value, Nullability.UNKNOWN); - // We want to clear all these types - if (!changed) { - changed = !collectTypesFromMeAndParents(value).isEmpty(); - } - return !changed - ? this - : new DelegatingDataFlowInfo( + putNullability(builder, value, Nullability.UNKNOWN); + return new DelegatingDataFlowInfo( this, ImmutableMap.copyOf(builder), EMPTY_TYPE_INFO, @@ -178,23 +170,23 @@ import static org.jetbrains.kotlin.resolve.calls.smartcasts.Nullability.NOT_NULL @Override @NotNull public DataFlowInfo assign(@NotNull DataFlowValue a, @NotNull DataFlowValue b) { - Map builder = Maps.newHashMap(); + Map nullability = Maps.newHashMap(); Nullability nullabilityOfB = getNullability(b); - boolean changed = putNullability(builder, a, nullabilityOfB); + putNullability(nullability, a, nullabilityOfB); + SetMultimap newTypeInfo = newTypeInfo(); - Set typesForA = collectTypesFromMeAndParents(a); Set typesForB = collectTypesFromMeAndParents(b); + // Own type of B must be recorded separately, e.g. for a constant + // But if its type is the same as A or it's null, there is no reason to do it + // because usually null type or own type are not saved in this set if (nullabilityOfB.canBeNonNull() && !a.getType().equals(b.getType())) { typesForB.add(b.getType()); } newTypeInfo.putAll(a, typesForB); - changed |= !typesForA.equals(typesForB); - return !changed - ? this - : new DelegatingDataFlowInfo( + return new DelegatingDataFlowInfo( this, - ImmutableMap.copyOf(builder), + ImmutableMap.copyOf(nullability), newTypeInfo.isEmpty() ? EMPTY_TYPE_INFO : newTypeInfo, a ); diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/smartcasts/SmartCastUtils.java b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/smartcasts/SmartCastUtils.java index 1ac8e36f94d..343b799d169 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/smartcasts/SmartCastUtils.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/smartcasts/SmartCastUtils.java @@ -179,11 +179,7 @@ public class SmartCastUtils { JetExpression expression = ((ExpressionReceiver) receiver).getExpression(); DataFlowValue dataFlowValue = DataFlowValueFactory.createDataFlowValue(receiver, context); - recordCastOrError(expression, - smartCastSubType, - context.trace, - dataFlowValue.isStableIdentifier() || dataFlowValue.isLocalVariable(), - true); + recordCastOrError(expression, smartCastSubType, context.trace, dataFlowValue.isPredictable(), true); return true; } diff --git a/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/ControlStructureTypingVisitor.java b/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/ControlStructureTypingVisitor.java index 9cc61e9bf13..ac1e13d52d9 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/ControlStructureTypingVisitor.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/ControlStructureTypingVisitor.java @@ -201,9 +201,10 @@ public class ControlStructureTypingVisitor extends ExpressionTypingVisitor { ExpressionTypingContext context = contextWithExpectedType.replaceExpectedType(NO_EXPECTED_TYPE).replaceContextDependency( INDEPENDENT); // Preliminary analysis - PreliminaryLoopVisitor loopVisitor = new PreliminaryLoopVisitor(expression); - loopVisitor.launch(); - context = context.replaceDataFlowInfo(loopVisitor.clearDataFlowInfoForAssignedLocalVariables(context.dataFlowInfo)); + PreliminaryLoopVisitor loopVisitor = PreliminaryLoopVisitor.visitLoop(expression); + context = context.replaceDataFlowInfo( + loopVisitor.clearDataFlowInfoForAssignedLocalVariables(context.dataFlowInfo) + ); JetExpression condition = expression.getCondition(); // Extract data flow info from condition itself without taking value into account @@ -288,9 +289,10 @@ public class ControlStructureTypingVisitor extends ExpressionTypingVisitor { JetExpression body = expression.getBody(); JetScope conditionScope = context.scope; // Preliminary analysis - PreliminaryLoopVisitor loopVisitor = new PreliminaryLoopVisitor(expression); - loopVisitor.launch(); - context = context.replaceDataFlowInfo(loopVisitor.clearDataFlowInfoForAssignedLocalVariables(context.dataFlowInfo)); + PreliminaryLoopVisitor loopVisitor = PreliminaryLoopVisitor.visitLoop(expression); + context = context.replaceDataFlowInfo( + loopVisitor.clearDataFlowInfoForAssignedLocalVariables(context.dataFlowInfo) + ); // Here we must record data flow information at the end of the body (or at the first jump, to be precise) and // .and it with entrance data flow information, because do-while body is executed at least once // See KT-6283 @@ -354,8 +356,7 @@ public class ControlStructureTypingVisitor extends ExpressionTypingVisitor { ExpressionTypingContext context = contextWithExpectedType.replaceExpectedType(NO_EXPECTED_TYPE).replaceContextDependency(INDEPENDENT); // Preliminary analysis - PreliminaryLoopVisitor loopVisitor = new PreliminaryLoopVisitor(expression); - loopVisitor.launch(); + PreliminaryLoopVisitor loopVisitor = PreliminaryLoopVisitor.visitLoop(expression); context = context.replaceDataFlowInfo(loopVisitor.clearDataFlowInfoForAssignedLocalVariables(context.dataFlowInfo)); JetExpression loopRange = expression.getLoopRange(); diff --git a/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/DataFlowUtils.java b/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/DataFlowUtils.java index d8dcbd64e22..106117e2822 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/DataFlowUtils.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/DataFlowUtils.java @@ -199,11 +199,7 @@ public class DataFlowUtils { for (JetType possibleType : c.dataFlowInfo.getPossibleTypes(dataFlowValue)) { if (JetTypeChecker.DEFAULT.isSubtypeOf(possibleType, c.expectedType)) { - SmartCastUtils.recordCastOrError(expression, - possibleType, - c.trace, - dataFlowValue.isStableIdentifier() || dataFlowValue.isLocalVariable(), - false); + SmartCastUtils.recordCastOrError(expression, possibleType, c.trace, dataFlowValue.isPredictable(), false); return possibleType; } } @@ -265,7 +261,7 @@ public class DataFlowUtils { ) { DataFlowValue dataFlowValue = DataFlowValueFactory.createDataFlowValue(expression, type, c); Collection possibleTypes = Sets.newHashSet(type); - if (dataFlowValue.isStableIdentifier()) { + if (dataFlowValue.isPredictable()) { possibleTypes.addAll(dataFlowInfo.getPossibleTypes(dataFlowValue)); } return possibleTypes; 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 1c3832a5fe9..fbb6a6c1315 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/ExpressionTypingVisitorForStatements.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/ExpressionTypingVisitorForStatements.java @@ -149,6 +149,8 @@ public class ExpressionTypingVisitorForStatements extends ExpressionTypingVisito propertyDescriptor, context.trace.getBindingContext(), DescriptorUtils.getContainingModuleOrNull(scope.getContainingDeclaration())); DataFlowValue initializerDataFlowValue = DataFlowValueFactory.createDataFlowValue(initializer, type, context); + // We cannot say here anything new about initializerDataFlowValue + // except it has the same value as variableDataFlowValue dataFlowInfo = dataFlowInfo.assign(variableDataFlowValue, initializerDataFlowValue); } } @@ -350,6 +352,7 @@ public class ExpressionTypingVisitorForStatements extends ExpressionTypingVisito if (left != null && leftType != null && rightType != null) { DataFlowValue leftValue = DataFlowValueFactory.createDataFlowValue(left, leftType, context); DataFlowValue rightValue = DataFlowValueFactory.createDataFlowValue(right, rightType, context); + // We cannot say here anything new about rightValue except it has the same value as leftValue dataFlowInfo = dataFlowInfo.assign(leftValue, rightValue); } } diff --git a/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/PreliminaryLoopVisitor.java b/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/PreliminaryLoopVisitor.java index c89ea9ec625..18b76ffae4c 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/PreliminaryLoopVisitor.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/PreliminaryLoopVisitor.java @@ -36,24 +36,26 @@ class PreliminaryLoopVisitor extends JetTreeVisitor { // loop under analysis private final JetLoopExpression loopExpression; - private Set assignedNames = new LinkedHashSet(); + private final Set assignedNames = new LinkedHashSet(); - public PreliminaryLoopVisitor(JetLoopExpression loopExpression) { + private PreliminaryLoopVisitor(@NotNull JetLoopExpression loopExpression) { this.loopExpression = loopExpression; } - public void launch() { - loopExpression.accept(this, null); + @NotNull + static public PreliminaryLoopVisitor visitLoop(@NotNull JetLoopExpression loopExpression) { + PreliminaryLoopVisitor visitor = new PreliminaryLoopVisitor(loopExpression); + loopExpression.accept(visitor, null); + return visitor; } - public DataFlowInfo clearDataFlowInfoForAssignedLocalVariables(DataFlowInfo dataFlowInfo) { + @NotNull + public DataFlowInfo clearDataFlowInfoForAssignedLocalVariables(@NotNull DataFlowInfo dataFlowInfo) { Map nullabilityMap = dataFlowInfo.getCompleteNullabilityInfo(); Set valueSetToClear = new LinkedHashSet(); for (DataFlowValue value: nullabilityMap.keySet()) { - // Only local variables which are not stable are under interest - if (value.isStableIdentifier() || !value.isLocalVariable()) - continue; - if (value.getId() instanceof LocalVariableDescriptor) { + // Only uncaptured local variables are under interest here + if (value.isUncapturedLocalVariable() && value.getId() instanceof LocalVariableDescriptor) { LocalVariableDescriptor descriptor = (LocalVariableDescriptor)value.getId(); if (assignedNames.contains(descriptor.getName())) { valueSetToClear.add(value); @@ -66,15 +68,14 @@ class PreliminaryLoopVisitor extends JetTreeVisitor { return dataFlowInfo; } - @Override - public Void visitLoopExpression(@NotNull JetLoopExpression loopExpression, Void arg) { - return super.visitLoopExpression(loopExpression, arg); - } - @Override public Void visitBinaryExpression(@NotNull JetBinaryExpression binaryExpression, Void arg) { - if (binaryExpression.getOperationToken() == JetTokens.EQ && binaryExpression.getLeft() instanceof JetNameReferenceExpression) { - assignedNames.add(((JetNameReferenceExpression) binaryExpression.getLeft()).getReferencedNameAsName()); + if (binaryExpression.getOperationToken() == JetTokens.EQ) { + JetExpression left = JetPsiUtil.deparenthesize(binaryExpression.getLeft()); + if (left instanceof JetNameReferenceExpression) { + // deparenthesize + assignedNames.add(((JetNameReferenceExpression) left).getReferencedNameAsName()); + } } return null; } diff --git a/compiler/testData/diagnostics/tests/Casts.kt b/compiler/testData/diagnostics/tests/Casts.kt index 3811dfe4cfa..31f02d508c8 100644 --- a/compiler/testData/diagnostics/tests/Casts.kt +++ b/compiler/testData/diagnostics/tests/Casts.kt @@ -8,7 +8,7 @@ fun test() : Unit { y as Int : Int x as Int? : Int? y as Int? : Int? - x as? Int : Int? + x as? Int : Int? y as? Int : Int? x as? Int? : Int? y as? Int? : Int? diff --git a/compiler/testData/diagnostics/tests/UnusedVariables.kt b/compiler/testData/diagnostics/tests/UnusedVariables.kt index a0d35a26f1c..7ad929ffd38 100644 --- a/compiler/testData/diagnostics/tests/UnusedVariables.kt +++ b/compiler/testData/diagnostics/tests/UnusedVariables.kt @@ -68,7 +68,7 @@ class MyTest() { } else { a = "ss" - doSmth(a as String) + doSmth(a) } doSmth(a) diff --git a/compiler/testData/diagnostics/tests/smartCasts/varnotnull/whileTrueWithBracketSet.kt b/compiler/testData/diagnostics/tests/smartCasts/varnotnull/whileTrueWithBracketSet.kt new file mode 100644 index 00000000000..46066b1dd96 --- /dev/null +++ b/compiler/testData/diagnostics/tests/smartCasts/varnotnull/whileTrueWithBracketSet.kt @@ -0,0 +1,13 @@ +fun x(): Boolean { return true } + +public fun foo(pp: String?): Int { + var p = pp + while(true) { + p!!.length() + if (x()) break + (((p))) = null + } + // Smart cast is NOT possible here + // (we could provide it but p = null makes it much harder) + return p.length() +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/smartCasts/varnotnull/whileTrueWithBracketSet.txt b/compiler/testData/diagnostics/tests/smartCasts/varnotnull/whileTrueWithBracketSet.txt new file mode 100644 index 00000000000..23bf0a2ca9a --- /dev/null +++ b/compiler/testData/diagnostics/tests/smartCasts/varnotnull/whileTrueWithBracketSet.txt @@ -0,0 +1,4 @@ +package + +public fun foo(/*0*/ pp: kotlin.String?): kotlin.Int +internal fun x(): kotlin.Boolean diff --git a/compiler/testData/diagnostics/tests/smartCasts/varnotnull/whileTrueWithBrackets.kt b/compiler/testData/diagnostics/tests/smartCasts/varnotnull/whileTrueWithBrackets.kt new file mode 100644 index 00000000000..fa06b703870 --- /dev/null +++ b/compiler/testData/diagnostics/tests/smartCasts/varnotnull/whileTrueWithBrackets.kt @@ -0,0 +1,13 @@ +fun x(): Boolean { return true } + +public fun foo(pp: String?): Int { + var p = pp + while(true) { + p!!.length() + if (x()) break + (p) = null + } + // Smart cast is NOT possible here + // (we could provide it but p = null makes it much harder) + return p.length() +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/smartCasts/varnotnull/whileTrueWithBrackets.txt b/compiler/testData/diagnostics/tests/smartCasts/varnotnull/whileTrueWithBrackets.txt new file mode 100644 index 00000000000..23bf0a2ca9a --- /dev/null +++ b/compiler/testData/diagnostics/tests/smartCasts/varnotnull/whileTrueWithBrackets.txt @@ -0,0 +1,4 @@ +package + +public fun foo(/*0*/ pp: kotlin.String?): kotlin.Int +internal fun x(): kotlin.Boolean diff --git a/compiler/tests/org/jetbrains/kotlin/checkers/JetDiagnosticsTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/JetDiagnosticsTestGenerated.java index 62634acfb42..1b9b3fff5ce 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/JetDiagnosticsTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/JetDiagnosticsTestGenerated.java @@ -11786,6 +11786,18 @@ public class JetDiagnosticsTestGenerated extends AbstractJetDiagnosticsTest { doTest(fileName); } + @TestMetadata("whileTrueWithBracketSet.kt") + public void testWhileTrueWithBracketSet() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/varnotnull/whileTrueWithBracketSet.kt"); + doTest(fileName); + } + + @TestMetadata("whileTrueWithBrackets.kt") + public void testWhileTrueWithBrackets() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/varnotnull/whileTrueWithBrackets.kt"); + doTest(fileName); + } + @TestMetadata("whileWithBreak.kt") public void testWhileWithBreak() throws Exception { String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/varnotnull/whileWithBreak.kt"); diff --git a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/DataFlowInfoUtilForCompletion.kt b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/DataFlowInfoUtilForCompletion.kt index 5d8943c50a4..789b877f5e0 100644 --- a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/DataFlowInfoUtilForCompletion.kt +++ b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/DataFlowInfoUtilForCompletion.kt @@ -24,7 +24,7 @@ import org.jetbrains.kotlin.descriptors.PackageViewDescriptor fun renderDataFlowValue(value: DataFlowValue): String? { // If it is not a stable identifier, there's no point in rendering it - if (!value.isStableIdentifier() && !value.isLocalVariable()) return null + if (!value.isPredictable()) return null fun renderId(id: Any?): String? { return when (id) {