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 77c9a5a2af2..cc0ab53dd40 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 @@ -27,23 +27,40 @@ import org.jetbrains.kotlin.builtins.KotlinBuiltIns; * In general case it's some r-value. */ public class DataFlowValue { - - public static final DataFlowValue NULL = new DataFlowValue(new Object(), KotlinBuiltIns.getInstance().getNullableNothingType(), false, false, Nullability.NULL); - public static final DataFlowValue NULLABLE = new DataFlowValue(new Object(), KotlinBuiltIns.getInstance().getNullableAnyType(), false, false, Nullability.UNKNOWN); - 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 uncapturedLocalVariable; + public enum Kind { + STABLE_VALUE("stable"), + PREDICTABLE_VARIABLE("predictable"), + UNPREDICTABLE_VARIABLE("unpredictable"), + OTHER("other"); + + @Override + public String toString() { + return name; + } + + Kind(String name) { + this.name = name; + } + + private final String name; + + public boolean isStable() { + return this == STABLE_VALUE; + } + } + + public static final DataFlowValue NULL = new DataFlowValue(new Object(), KotlinBuiltIns.getInstance().getNullableNothingType(), Kind.OTHER, Nullability.NULL); + public static final DataFlowValue ERROR = new DataFlowValue(new Object(), ErrorUtils.createErrorType("Error type for data flow"), Kind.OTHER, Nullability.IMPOSSIBLE); + + private final Kind kind; private final JetType type; private final Object id; private final Nullability immanentNullability; // Use DataFlowValueFactory - /*package*/ DataFlowValue(Object id, JetType type, boolean stableIdentifier, boolean uncapturedLocalVariable, Nullability immanentNullability) { - assert !stableIdentifier || !uncapturedLocalVariable : - "data flow value for object " + id + " cannot be together a stable identifier and an uncaptured local variable"; - this.stableIdentifier = stableIdentifier; - this.uncapturedLocalVariable = uncapturedLocalVariable; + /*package*/ DataFlowValue(Object id, JetType type, Kind kind, Nullability immanentNullability) { + this.kind = kind; this.type = type; this.id = id; this.immanentNullability = immanentNullability; @@ -59,30 +76,17 @@ public class DataFlowValue { return immanentNullability; } - /** - * 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 - */ - private boolean isStableIdentifier() { - return stableIdentifier; + public Kind getKind() { + return kind; } /** - * Identifier is considered a local variable here if it's mutable (var), local and not captured in a closure - */ - public boolean isUncapturedLocalVariable() { - return uncapturedLocalVariable; - } - - /** - * Both stable identifiers and uncaptured local variables are considered "predictable". + * Both stable values and local variables (regardless captured or not) 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. + * like accessing mutable properties in another thread. */ public boolean isPredictable() { - return stableIdentifier || uncapturedLocalVariable; + return kind == Kind.STABLE_VALUE || kind == Kind.PREDICTABLE_VARIABLE; } @NotNull @@ -97,7 +101,7 @@ public class DataFlowValue { DataFlowValue that = (DataFlowValue) o; - if (stableIdentifier != that.stableIdentifier) return false; + if (kind.isStable() != that.kind.isStable()) return false; if (id != null ? !id.equals(that.id) : that.id != null) return false; if (type != null ? !type.equals(that.type) : that.type != null) return false; @@ -106,12 +110,12 @@ public class DataFlowValue { @Override public String toString() { - return (stableIdentifier ? "stable " : "unstable ") + (id == null ? null : id.toString()) + " " + immanentNullability; + return kind.toString() + (id == null ? null : id.toString()) + " " + immanentNullability; } @Override public int hashCode() { - int result = (stableIdentifier ? 1 : 0); + int result = kind.isStable() ? 1 : 0; result = 31 * result + (type != null ? type.hashCode() : 0); result = 31 * result + (id != null ? id.hashCode() : 0); return result; 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 40d56e3c2ea..01d6d1ec451 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 @@ -81,23 +81,21 @@ public class DataFlowValueFactory { // fun foo(x: T) = x!!.hashCode() // there no way in type system to denote that `x!!` is not nullable return new DataFlowValue(expression, type, - /* stableIdentifier = */false, - /* uncapturedLocalVariable = */false, + DataFlowValue.Kind.OTHER, Nullability.NOT_NULL); } IdentifierInfo result = getIdForStableIdentifier(expression, bindingContext, containingDeclarationOrModule); return new DataFlowValue(result == NO_IDENTIFIER_INFO ? expression : result.id, type, - result.isStable, - result.isLocal, + result.kind, getImmanentNullability(type)); } @NotNull public static DataFlowValue createDataFlowValue(@NotNull ThisReceiver receiver) { JetType type = receiver.getType(); - return new DataFlowValue(receiver, type, true, false, getImmanentNullability(type)); + return new DataFlowValue(receiver, type, DataFlowValue.Kind.STABLE_VALUE, getImmanentNullability(type)); } @NotNull @@ -118,7 +116,7 @@ public class DataFlowValueFactory { if (receiverValue instanceof TransientReceiver || receiverValue instanceof ScriptReceiver) { // SCRIPT: smartcasts data flow JetType type = receiverValue.getType(); - return new DataFlowValue(receiverValue, type, true, false, getImmanentNullability(type)); + return new DataFlowValue(receiverValue, type, DataFlowValue.Kind.STABLE_VALUE, getImmanentNullability(type)); } else if (receiverValue instanceof ClassReceiver || receiverValue instanceof ExtensionReceiver) { return createDataFlowValue((ThisReceiver) receiverValue); @@ -145,8 +143,7 @@ public class DataFlowValueFactory { ) { JetType type = variableDescriptor.getType(); return new DataFlowValue(variableDescriptor, type, - isStableVariable(variableDescriptor, usageContainingModule), - isUncapturedLocalVariable(variableDescriptor, bindingContext), + variableKind(variableDescriptor, usageContainingModule, bindingContext), getImmanentNullability(type)); } @@ -157,20 +154,17 @@ public class DataFlowValueFactory { private static class IdentifierInfo { public final Object id; - public final boolean isStable; - public final boolean isLocal; + public final DataFlowValue.Kind kind; public final boolean isPackage; - private IdentifierInfo(Object id, boolean isStable, boolean isLocal, boolean isPackage) { - assert !isStable || !isLocal : "Identifier info for object " + id + " cannot be stable and local at one time"; + private IdentifierInfo(Object id, DataFlowValue.Kind kind, boolean isPackage) { this.id = id; - this.isStable = isStable; - this.isLocal = isLocal; + this.kind = kind; this.isPackage = isPackage; } } - private static final IdentifierInfo NO_IDENTIFIER_INFO = new IdentifierInfo(null, false, false, false) { + private static final IdentifierInfo NO_IDENTIFIER_INFO = new IdentifierInfo(null, DataFlowValue.Kind.OTHER, false) { @Override public String toString() { return "NO_IDENTIFIER_INFO"; @@ -178,18 +172,18 @@ public class DataFlowValueFactory { }; @NotNull - private static IdentifierInfo createInfo(Object id, boolean isStable, boolean isLocal) { - return new IdentifierInfo(id, isStable, isLocal, false); + private static IdentifierInfo createInfo(Object id, DataFlowValue.Kind kind) { + return new IdentifierInfo(id, kind, false); } @NotNull private static IdentifierInfo createStableInfo(Object id) { - return createInfo(id, true, false); + return createInfo(id, DataFlowValue.Kind.STABLE_VALUE); } @NotNull private static IdentifierInfo createPackageOrClassInfo(Object id) { - return new IdentifierInfo(id, true, false, true); + return new IdentifierInfo(id, DataFlowValue.Kind.STABLE_VALUE, true); } @NotNull @@ -201,9 +195,10 @@ public class DataFlowValueFactory { return selectorInfo; } return createInfo(Pair.create(receiverInfo.id, selectorInfo.id), - receiverInfo.isStable && selectorInfo.isStable, + receiverInfo.kind.isStable() && selectorInfo.kind.isStable() + ? DataFlowValue.Kind.STABLE_VALUE // x.y can never be a local variable - false); + : DataFlowValue.Kind.OTHER); } @NotNull @@ -211,7 +206,7 @@ public class DataFlowValueFactory { if (argumentInfo == NO_IDENTIFIER_INFO) { return NO_IDENTIFIER_INFO; } - return createInfo(Pair.create(expression, argumentInfo.id), argumentInfo.isStable, argumentInfo.isLocal); + return createInfo(Pair.create(expression, argumentInfo.id), argumentInfo.kind); } @NotNull @@ -278,8 +273,7 @@ public class DataFlowValueFactory { VariableDescriptor variableDescriptor = (VariableDescriptor) declarationDescriptor; return combineInfo(receiverInfo, createInfo(variableDescriptor, - isStableVariable(variableDescriptor, usageModuleDescriptor), - isUncapturedLocalVariable(variableDescriptor, bindingContext))); + variableKind(variableDescriptor, usageModuleDescriptor, bindingContext))); } if (declarationDescriptor instanceof PackageViewDescriptor || declarationDescriptor instanceof ClassDescriptor) { return createPackageOrClassInfo(declarationDescriptor); @@ -314,10 +308,18 @@ public class DataFlowValueFactory { return NO_IDENTIFIER_INFO; } - public static boolean isUncapturedLocalVariable(@NotNull VariableDescriptor variableDescriptor, @NotNull BindingContext bindingContext) { - return variableDescriptor.isVar() - && variableDescriptor instanceof LocalVariableDescriptor - && !BindingContextUtils.isVarCapturedInClosure(bindingContext, variableDescriptor); + public static DataFlowValue.Kind variableKind( + @NotNull VariableDescriptor variableDescriptor, + @Nullable ModuleDescriptor usageModule, + @NotNull BindingContext bindingContext + ) { + if (isStableVariable(variableDescriptor, usageModule)) return DataFlowValue.Kind.STABLE_VALUE; + boolean isLocalVar = variableDescriptor.isVar() && variableDescriptor instanceof LocalVariableDescriptor; + if (!isLocalVar) return DataFlowValue.Kind.OTHER; + if (BindingContextUtils.isVarCapturedInClosure(bindingContext, variableDescriptor)) { + return DataFlowValue.Kind.UNPREDICTABLE_VARIABLE; + } + return DataFlowValue.Kind.PREDICTABLE_VARIABLE; } /** 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 b5e99e035f6..81112e97189 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/PreliminaryLoopVisitor.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/PreliminaryLoopVisitor.java @@ -43,8 +43,8 @@ class PreliminaryLoopVisitor extends AssignedVariablesSearcher { Map nullabilityMap = dataFlowInfo.getCompleteNullabilityInfo(); Set valueSetToClear = new LinkedHashSet(); for (DataFlowValue value: nullabilityMap.keySet()) { - // Only uncaptured local variables are under interest here - if (value.isUncapturedLocalVariable() && value.getId() instanceof LocalVariableDescriptor) { + // Only predictable variables are under interest here + if (value.getKind() == DataFlowValue.Kind.PREDICTABLE_VARIABLE && value.getId() instanceof LocalVariableDescriptor) { LocalVariableDescriptor descriptor = (LocalVariableDescriptor)value.getId(); if (getAssignedNames().contains(descriptor.getName())) { valueSetToClear.add(value);