Review fixes for mutable variable smart casting (mostly refactoring).

DataFlowValue.isPredictable() introduced instead of isStableIdentifier().
Parenthesized assignments are treated correctly.
This commit is contained in:
Mikhail Glukhikh
2015-04-16 20:05:46 +03:00
parent e97e792674
commit 109e0abb6d
17 changed files with 133 additions and 81 deletions
@@ -446,7 +446,7 @@ public class CandidateResolver {
if (deparenthesizedArgument == null || type == null) return type; if (deparenthesizedArgument == null || type == null) return type;
DataFlowValue dataFlowValue = DataFlowValueFactory.createDataFlowValue(deparenthesizedArgument, type, context); DataFlowValue dataFlowValue = DataFlowValueFactory.createDataFlowValue(deparenthesizedArgument, type, context);
if (!dataFlowValue.isStableIdentifier() && !dataFlowValue.isLocalVariable()) return type; if (!dataFlowValue.isPredictable()) return type;
Set<JetType> possibleTypes = context.dataFlowInfo.getPossibleTypes(dataFlowValue); Set<JetType> possibleTypes = context.dataFlowInfo.getPossibleTypes(dataFlowValue);
if (possibleTypes.isEmpty()) return type; if (possibleTypes.isEmpty()) return type;
@@ -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); 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 stableIdentifier;
private final boolean localVariable; private final boolean uncapturedlocalVariable;
private final JetType type; private final JetType type;
private final Object id; private final Object id;
private final Nullability immanentNullability; private final Nullability immanentNullability;
// Use DataFlowValueFactory // 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.stableIdentifier = stableIdentifier;
this.localVariable = localVariable; this.uncapturedlocalVariable = uncapturedlocalVariable;
this.type = type; this.type = type;
this.id = id; this.id = id;
this.immanentNullability = immanentNullability; this.immanentNullability = immanentNullability;
@@ -59,16 +61,28 @@ public class DataFlowValue {
/** /**
* Stable identifier is a non-literal value that is statically known to be immutable * 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; return stableIdentifier;
} }
/** /**
* Identifier is considered a local variable here if it's mutable (var), local and not captured in a closure * Identifier is considered a local variable here if it's mutable (var), local and not captured in a closure
*/ */
public boolean isLocalVariable() { public boolean isUncapturedLocalVariable() {
return localVariable; 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 @NotNull
@@ -130,7 +130,7 @@ public class DataFlowValueFactory {
JetType type = variableDescriptor.getType(); JetType type = variableDescriptor.getType();
return new DataFlowValue(variableDescriptor, type, return new DataFlowValue(variableDescriptor, type,
isStableVariable(variableDescriptor, usageContainingModule), isStableVariable(variableDescriptor, usageContainingModule),
isLocalVariable(variableDescriptor, bindingContext), isUncapturedLocalVariable(variableDescriptor, bindingContext),
getImmanentNullability(type)); getImmanentNullability(type));
} }
@@ -165,6 +165,11 @@ public class DataFlowValueFactory {
return new IdentifierInfo(id, isStable, isLocal, false); return new IdentifierInfo(id, isStable, isLocal, false);
} }
@NotNull
private static IdentifierInfo createStableInfo(Object id) {
return createInfo(id, true, false);
}
@NotNull @NotNull
private static IdentifierInfo createPackageInfo(Object id) { private static IdentifierInfo createPackageInfo(Object id) {
return new IdentifierInfo(id, true, false, true); return new IdentifierInfo(id, true, false, true);
@@ -180,6 +185,7 @@ public class DataFlowValueFactory {
} }
return createInfo(Pair.create(receiverInfo.id, selectorInfo.id), return createInfo(Pair.create(receiverInfo.id, selectorInfo.id),
receiverInfo.isStable && selectorInfo.isStable, receiverInfo.isStable && selectorInfo.isStable,
// x.y can never be a local variable
false); false);
} }
@@ -240,7 +246,7 @@ public class DataFlowValueFactory {
VariableDescriptor variableDescriptor = (VariableDescriptor) declarationDescriptor; VariableDescriptor variableDescriptor = (VariableDescriptor) declarationDescriptor;
return combineInfo(receiverInfo, createInfo(variableDescriptor, return combineInfo(receiverInfo, createInfo(variableDescriptor,
isStableVariable(variableDescriptor, usageModuleDescriptor), isStableVariable(variableDescriptor, usageModuleDescriptor),
isLocalVariable(variableDescriptor, bindingContext))); isUncapturedLocalVariable(variableDescriptor, bindingContext)));
} }
if (declarationDescriptor instanceof PackageViewDescriptor) { if (declarationDescriptor instanceof PackageViewDescriptor) {
return createPackageInfo(declarationDescriptor); return createPackageInfo(declarationDescriptor);
@@ -267,21 +273,18 @@ public class DataFlowValueFactory {
ReceiverParameterDescriptor receiverParameter = ((CallableDescriptor) descriptorOfThisReceiver).getExtensionReceiverParameter(); ReceiverParameterDescriptor receiverParameter = ((CallableDescriptor) descriptorOfThisReceiver).getExtensionReceiverParameter();
assert receiverParameter != null : "'This' refers to the callable member without a receiver parameter: " + assert receiverParameter != null : "'This' refers to the callable member without a receiver parameter: " +
descriptorOfThisReceiver; descriptorOfThisReceiver;
return createInfo(receiverParameter.getValue(), true, false); return createStableInfo(receiverParameter.getValue());
} }
if (descriptorOfThisReceiver instanceof ClassDescriptor) { if (descriptorOfThisReceiver instanceof ClassDescriptor) {
return createInfo(((ClassDescriptor) descriptorOfThisReceiver).getThisAsReceiverParameter().getValue(), true, false); return createStableInfo(((ClassDescriptor) descriptorOfThisReceiver).getThisAsReceiverParameter().getValue());
} }
return NO_IDENTIFIER_INFO; return NO_IDENTIFIER_INFO;
} }
public static boolean isLocalVariable(@NotNull VariableDescriptor variableDescriptor, @NotNull BindingContext bindingContext) { public static boolean isUncapturedLocalVariable(@NotNull VariableDescriptor variableDescriptor, @NotNull BindingContext bindingContext) {
if (variableDescriptor.isVar() && variableDescriptor instanceof LocalVariableDescriptor) { return variableDescriptor.isVar()
if (BindingContextUtils.isVarCapturedInClosure(bindingContext, variableDescriptor)) && variableDescriptor instanceof LocalVariableDescriptor
return false; && !BindingContextUtils.isVarCapturedInClosure(bindingContext, variableDescriptor);
return true;
}
return false;
} }
/** /**
@@ -39,14 +39,12 @@ import static org.jetbrains.kotlin.resolve.calls.smartcasts.Nullability.NOT_NULL
@NotNull @NotNull
private final ImmutableMap<DataFlowValue, Nullability> nullabilityInfo; private final ImmutableMap<DataFlowValue, Nullability> nullabilityInfo;
/** // Also immutable
* Also immutable
*/
@NotNull @NotNull
private final SetMultimap<DataFlowValue, JetType> typeInfo; private final SetMultimap<DataFlowValue, JetType> 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 * so parent type info should not be in use
*/ */
@Nullable @Nullable
@@ -94,16 +92,16 @@ import static org.jetbrains.kotlin.resolve.calls.smartcasts.Nullability.NOT_NULL
@NotNull @NotNull
public SetMultimap<DataFlowValue, JetType> getCompleteTypeInfo() { public SetMultimap<DataFlowValue, JetType> getCompleteTypeInfo() {
SetMultimap<DataFlowValue, JetType> result = newTypeInfo(); SetMultimap<DataFlowValue, JetType> result = newTypeInfo();
Set<DataFlowValue> resultCompleted = new HashSet<DataFlowValue>(); Set<DataFlowValue> withGivenTypeInfo = new HashSet<DataFlowValue>();
DelegatingDataFlowInfo info = this; DelegatingDataFlowInfo info = this;
while (info != null) { while (info != null) {
for (DataFlowValue key : info.typeInfo.keySet()) { for (DataFlowValue key : info.typeInfo.keySet()) {
if (!resultCompleted.contains(key)) { if (!withGivenTypeInfo.contains(key)) {
result.putAll(key, info.typeInfo.get(key)); result.putAll(key, info.typeInfo.get(key));
} }
} }
if (valueWithGivenTypeInfo != null) { if (valueWithGivenTypeInfo != null) {
resultCompleted.add(valueWithGivenTypeInfo); withGivenTypeInfo.add(valueWithGivenTypeInfo);
} }
info = (DelegatingDataFlowInfo) info.parent; info = (DelegatingDataFlowInfo) info.parent;
} }
@@ -113,7 +111,7 @@ import static org.jetbrains.kotlin.resolve.calls.smartcasts.Nullability.NOT_NULL
@Override @Override
@NotNull @NotNull
public Nullability getNullability(@NotNull DataFlowValue key) { 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); Nullability nullability = nullabilityInfo.get(key);
return nullability != null ? nullability : return nullability != null ? nullability :
parent != null ? parent.getNullability(key) : parent != null ? parent.getNullability(key) :
@@ -125,7 +123,7 @@ import static org.jetbrains.kotlin.resolve.calls.smartcasts.Nullability.NOT_NULL
@NotNull DataFlowValue value, @NotNull DataFlowValue value,
@NotNull Nullability nullability @NotNull Nullability nullability
) { ) {
if (!value.isStableIdentifier() && !value.isLocalVariable()) return false; if (!value.isPredictable()) return false;
map.put(value, nullability); map.put(value, nullability);
return nullability != getNullability(value); return nullability != getNullability(value);
} }
@@ -160,14 +158,8 @@ import static org.jetbrains.kotlin.resolve.calls.smartcasts.Nullability.NOT_NULL
@NotNull @NotNull
public DataFlowInfo clearValueInfo(@NotNull DataFlowValue value) { public DataFlowInfo clearValueInfo(@NotNull DataFlowValue value) {
Map<DataFlowValue, Nullability> builder = Maps.newHashMap(); Map<DataFlowValue, Nullability> builder = Maps.newHashMap();
boolean changed = putNullability(builder, value, Nullability.UNKNOWN); putNullability(builder, value, Nullability.UNKNOWN);
// We want to clear all these types return new DelegatingDataFlowInfo(
if (!changed) {
changed = !collectTypesFromMeAndParents(value).isEmpty();
}
return !changed
? this
: new DelegatingDataFlowInfo(
this, this,
ImmutableMap.copyOf(builder), ImmutableMap.copyOf(builder),
EMPTY_TYPE_INFO, EMPTY_TYPE_INFO,
@@ -178,23 +170,23 @@ import static org.jetbrains.kotlin.resolve.calls.smartcasts.Nullability.NOT_NULL
@Override @Override
@NotNull @NotNull
public DataFlowInfo assign(@NotNull DataFlowValue a, @NotNull DataFlowValue b) { public DataFlowInfo assign(@NotNull DataFlowValue a, @NotNull DataFlowValue b) {
Map<DataFlowValue, Nullability> builder = Maps.newHashMap(); Map<DataFlowValue, Nullability> nullability = Maps.newHashMap();
Nullability nullabilityOfB = getNullability(b); Nullability nullabilityOfB = getNullability(b);
boolean changed = putNullability(builder, a, nullabilityOfB); putNullability(nullability, a, nullabilityOfB);
SetMultimap<DataFlowValue, JetType> newTypeInfo = newTypeInfo(); SetMultimap<DataFlowValue, JetType> newTypeInfo = newTypeInfo();
Set<JetType> typesForA = collectTypesFromMeAndParents(a);
Set<JetType> typesForB = collectTypesFromMeAndParents(b); Set<JetType> 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())) { if (nullabilityOfB.canBeNonNull() && !a.getType().equals(b.getType())) {
typesForB.add(b.getType()); typesForB.add(b.getType());
} }
newTypeInfo.putAll(a, typesForB); newTypeInfo.putAll(a, typesForB);
changed |= !typesForA.equals(typesForB);
return !changed return new DelegatingDataFlowInfo(
? this
: new DelegatingDataFlowInfo(
this, this,
ImmutableMap.copyOf(builder), ImmutableMap.copyOf(nullability),
newTypeInfo.isEmpty() ? EMPTY_TYPE_INFO : newTypeInfo, newTypeInfo.isEmpty() ? EMPTY_TYPE_INFO : newTypeInfo,
a a
); );
@@ -179,11 +179,7 @@ public class SmartCastUtils {
JetExpression expression = ((ExpressionReceiver) receiver).getExpression(); JetExpression expression = ((ExpressionReceiver) receiver).getExpression();
DataFlowValue dataFlowValue = DataFlowValueFactory.createDataFlowValue(receiver, context); DataFlowValue dataFlowValue = DataFlowValueFactory.createDataFlowValue(receiver, context);
recordCastOrError(expression, recordCastOrError(expression, smartCastSubType, context.trace, dataFlowValue.isPredictable(), true);
smartCastSubType,
context.trace,
dataFlowValue.isStableIdentifier() || dataFlowValue.isLocalVariable(),
true);
return true; return true;
} }
@@ -201,9 +201,10 @@ public class ControlStructureTypingVisitor extends ExpressionTypingVisitor {
ExpressionTypingContext context = contextWithExpectedType.replaceExpectedType(NO_EXPECTED_TYPE).replaceContextDependency( ExpressionTypingContext context = contextWithExpectedType.replaceExpectedType(NO_EXPECTED_TYPE).replaceContextDependency(
INDEPENDENT); INDEPENDENT);
// Preliminary analysis // Preliminary analysis
PreliminaryLoopVisitor loopVisitor = new PreliminaryLoopVisitor(expression); PreliminaryLoopVisitor loopVisitor = PreliminaryLoopVisitor.visitLoop(expression);
loopVisitor.launch(); context = context.replaceDataFlowInfo(
context = context.replaceDataFlowInfo(loopVisitor.clearDataFlowInfoForAssignedLocalVariables(context.dataFlowInfo)); loopVisitor.clearDataFlowInfoForAssignedLocalVariables(context.dataFlowInfo)
);
JetExpression condition = expression.getCondition(); JetExpression condition = expression.getCondition();
// Extract data flow info from condition itself without taking value into account // 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(); JetExpression body = expression.getBody();
JetScope conditionScope = context.scope; JetScope conditionScope = context.scope;
// Preliminary analysis // Preliminary analysis
PreliminaryLoopVisitor loopVisitor = new PreliminaryLoopVisitor(expression); PreliminaryLoopVisitor loopVisitor = PreliminaryLoopVisitor.visitLoop(expression);
loopVisitor.launch(); context = context.replaceDataFlowInfo(
context = context.replaceDataFlowInfo(loopVisitor.clearDataFlowInfoForAssignedLocalVariables(context.dataFlowInfo)); 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 // 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 // .and it with entrance data flow information, because do-while body is executed at least once
// See KT-6283 // See KT-6283
@@ -354,8 +356,7 @@ public class ControlStructureTypingVisitor extends ExpressionTypingVisitor {
ExpressionTypingContext context = ExpressionTypingContext context =
contextWithExpectedType.replaceExpectedType(NO_EXPECTED_TYPE).replaceContextDependency(INDEPENDENT); contextWithExpectedType.replaceExpectedType(NO_EXPECTED_TYPE).replaceContextDependency(INDEPENDENT);
// Preliminary analysis // Preliminary analysis
PreliminaryLoopVisitor loopVisitor = new PreliminaryLoopVisitor(expression); PreliminaryLoopVisitor loopVisitor = PreliminaryLoopVisitor.visitLoop(expression);
loopVisitor.launch();
context = context.replaceDataFlowInfo(loopVisitor.clearDataFlowInfoForAssignedLocalVariables(context.dataFlowInfo)); context = context.replaceDataFlowInfo(loopVisitor.clearDataFlowInfoForAssignedLocalVariables(context.dataFlowInfo));
JetExpression loopRange = expression.getLoopRange(); JetExpression loopRange = expression.getLoopRange();
@@ -199,11 +199,7 @@ public class DataFlowUtils {
for (JetType possibleType : c.dataFlowInfo.getPossibleTypes(dataFlowValue)) { for (JetType possibleType : c.dataFlowInfo.getPossibleTypes(dataFlowValue)) {
if (JetTypeChecker.DEFAULT.isSubtypeOf(possibleType, c.expectedType)) { if (JetTypeChecker.DEFAULT.isSubtypeOf(possibleType, c.expectedType)) {
SmartCastUtils.recordCastOrError(expression, SmartCastUtils.recordCastOrError(expression, possibleType, c.trace, dataFlowValue.isPredictable(), false);
possibleType,
c.trace,
dataFlowValue.isStableIdentifier() || dataFlowValue.isLocalVariable(),
false);
return possibleType; return possibleType;
} }
} }
@@ -265,7 +261,7 @@ public class DataFlowUtils {
) { ) {
DataFlowValue dataFlowValue = DataFlowValueFactory.createDataFlowValue(expression, type, c); DataFlowValue dataFlowValue = DataFlowValueFactory.createDataFlowValue(expression, type, c);
Collection<JetType> possibleTypes = Sets.newHashSet(type); Collection<JetType> possibleTypes = Sets.newHashSet(type);
if (dataFlowValue.isStableIdentifier()) { if (dataFlowValue.isPredictable()) {
possibleTypes.addAll(dataFlowInfo.getPossibleTypes(dataFlowValue)); possibleTypes.addAll(dataFlowInfo.getPossibleTypes(dataFlowValue));
} }
return possibleTypes; return possibleTypes;
@@ -149,6 +149,8 @@ public class ExpressionTypingVisitorForStatements extends ExpressionTypingVisito
propertyDescriptor, context.trace.getBindingContext(), propertyDescriptor, context.trace.getBindingContext(),
DescriptorUtils.getContainingModuleOrNull(scope.getContainingDeclaration())); DescriptorUtils.getContainingModuleOrNull(scope.getContainingDeclaration()));
DataFlowValue initializerDataFlowValue = DataFlowValueFactory.createDataFlowValue(initializer, type, context); 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); dataFlowInfo = dataFlowInfo.assign(variableDataFlowValue, initializerDataFlowValue);
} }
} }
@@ -350,6 +352,7 @@ public class ExpressionTypingVisitorForStatements extends ExpressionTypingVisito
if (left != null && leftType != null && rightType != null) { if (left != null && leftType != null && rightType != null) {
DataFlowValue leftValue = DataFlowValueFactory.createDataFlowValue(left, leftType, context); DataFlowValue leftValue = DataFlowValueFactory.createDataFlowValue(left, leftType, context);
DataFlowValue rightValue = DataFlowValueFactory.createDataFlowValue(right, rightType, 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); dataFlowInfo = dataFlowInfo.assign(leftValue, rightValue);
} }
} }
@@ -36,24 +36,26 @@ class PreliminaryLoopVisitor extends JetTreeVisitor<Void> {
// loop under analysis // loop under analysis
private final JetLoopExpression loopExpression; private final JetLoopExpression loopExpression;
private Set<Name> assignedNames = new LinkedHashSet<Name>(); private final Set<Name> assignedNames = new LinkedHashSet<Name>();
public PreliminaryLoopVisitor(JetLoopExpression loopExpression) { private PreliminaryLoopVisitor(@NotNull JetLoopExpression loopExpression) {
this.loopExpression = loopExpression; this.loopExpression = loopExpression;
} }
public void launch() { @NotNull
loopExpression.accept(this, null); 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<DataFlowValue, Nullability> nullabilityMap = dataFlowInfo.getCompleteNullabilityInfo(); Map<DataFlowValue, Nullability> nullabilityMap = dataFlowInfo.getCompleteNullabilityInfo();
Set<DataFlowValue> valueSetToClear = new LinkedHashSet<DataFlowValue>(); Set<DataFlowValue> valueSetToClear = new LinkedHashSet<DataFlowValue>();
for (DataFlowValue value: nullabilityMap.keySet()) { for (DataFlowValue value: nullabilityMap.keySet()) {
// Only local variables which are not stable are under interest // Only uncaptured local variables are under interest here
if (value.isStableIdentifier() || !value.isLocalVariable()) if (value.isUncapturedLocalVariable() && value.getId() instanceof LocalVariableDescriptor) {
continue;
if (value.getId() instanceof LocalVariableDescriptor) {
LocalVariableDescriptor descriptor = (LocalVariableDescriptor)value.getId(); LocalVariableDescriptor descriptor = (LocalVariableDescriptor)value.getId();
if (assignedNames.contains(descriptor.getName())) { if (assignedNames.contains(descriptor.getName())) {
valueSetToClear.add(value); valueSetToClear.add(value);
@@ -66,15 +68,14 @@ class PreliminaryLoopVisitor extends JetTreeVisitor<Void> {
return dataFlowInfo; return dataFlowInfo;
} }
@Override
public Void visitLoopExpression(@NotNull JetLoopExpression loopExpression, Void arg) {
return super.visitLoopExpression(loopExpression, arg);
}
@Override @Override
public Void visitBinaryExpression(@NotNull JetBinaryExpression binaryExpression, Void arg) { public Void visitBinaryExpression(@NotNull JetBinaryExpression binaryExpression, Void arg) {
if (binaryExpression.getOperationToken() == JetTokens.EQ && binaryExpression.getLeft() instanceof JetNameReferenceExpression) { if (binaryExpression.getOperationToken() == JetTokens.EQ) {
assignedNames.add(((JetNameReferenceExpression) binaryExpression.getLeft()).getReferencedNameAsName()); JetExpression left = JetPsiUtil.deparenthesize(binaryExpression.getLeft());
if (left instanceof JetNameReferenceExpression) {
// deparenthesize
assignedNames.add(((JetNameReferenceExpression) left).getReferencedNameAsName());
}
} }
return null; return null;
} }
+1 -1
View File
@@ -8,7 +8,7 @@ fun test() : Unit {
y <!USELESS_CAST!>as Int<!> : Int y <!USELESS_CAST!>as Int<!> : Int
x <!USELESS_CAST!>as Int?<!> : Int? x <!USELESS_CAST!>as Int?<!> : Int?
y <!USELESS_CAST_STATIC_ASSERT_IS_FINE!>as Int?<!> : Int? y <!USELESS_CAST_STATIC_ASSERT_IS_FINE!>as Int?<!> : Int?
x as? Int : Int? x <!USELESS_CAST_STATIC_ASSERT_IS_FINE!>as? Int<!> : Int?
y <!USELESS_CAST!>as? Int<!> : Int? y <!USELESS_CAST!>as? Int<!> : Int?
x <!USELESS_CAST!>as? Int?<!> : Int? x <!USELESS_CAST!>as? Int?<!> : Int?
y <!USELESS_CAST_STATIC_ASSERT_IS_FINE!>as? Int?<!> : Int? y <!USELESS_CAST_STATIC_ASSERT_IS_FINE!>as? Int?<!> : Int?
@@ -68,7 +68,7 @@ class MyTest() {
} }
else { else {
a = "ss" a = "ss"
doSmth(a as String) doSmth(<!DEBUG_INFO_SMARTCAST!>a<!>)
} }
doSmth(a) doSmth(a)
@@ -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<!UNSAFE_CALL!>.<!>length()
}
@@ -0,0 +1,4 @@
package
public fun foo(/*0*/ pp: kotlin.String?): kotlin.Int
internal fun x(): kotlin.Boolean
@@ -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<!UNSAFE_CALL!>.<!>length()
}
@@ -0,0 +1,4 @@
package
public fun foo(/*0*/ pp: kotlin.String?): kotlin.Int
internal fun x(): kotlin.Boolean
@@ -11786,6 +11786,18 @@ public class JetDiagnosticsTestGenerated extends AbstractJetDiagnosticsTest {
doTest(fileName); 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") @TestMetadata("whileWithBreak.kt")
public void testWhileWithBreak() throws Exception { public void testWhileWithBreak() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/varnotnull/whileWithBreak.kt"); String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/varnotnull/whileWithBreak.kt");
@@ -24,7 +24,7 @@ import org.jetbrains.kotlin.descriptors.PackageViewDescriptor
fun renderDataFlowValue(value: DataFlowValue): String? { fun renderDataFlowValue(value: DataFlowValue): String? {
// If it is not a stable identifier, there's no point in rendering it // 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? { fun renderId(id: Any?): String? {
return when (id) { return when (id) {