Nullability analysis supported for simple cases

This commit is contained in:
Andrey Breslav
2011-06-10 17:06:35 +04:00
parent accb2dd89d
commit 41322a8658
3 changed files with 189 additions and 45 deletions
@@ -1,10 +1,13 @@
package org.jetbrains.jet.lang.types;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.jet.lang.descriptors.VariableDescriptor;
import java.util.Map;
/**
* @author abreslav
*/
@@ -16,6 +19,26 @@ public class DataFlowInfo {
return EMPTY;
}
public static abstract class CompositionOperator {
public abstract DataFlowInfo compose(DataFlowInfo a, DataFlowInfo b);
}
public static final CompositionOperator AND = new CompositionOperator() {
@Override
public DataFlowInfo compose(DataFlowInfo a, DataFlowInfo b) {
return a.and(b);
}
};
public static final CompositionOperator OR = new CompositionOperator() {
@Override
public DataFlowInfo compose(DataFlowInfo a, DataFlowInfo b) {
return a.or(b);
}
};
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
private final ImmutableMap<VariableDescriptor, NullabilityFlags> nullabilityInfo;
public DataFlowInfo(ImmutableMap<VariableDescriptor, NullabilityFlags> nullabilityInfo) {
@@ -35,10 +58,39 @@ public class DataFlowInfo {
}
public DataFlowInfo equalsToNull(@NotNull VariableDescriptor variableDescriptor, boolean notNull) {
ImmutableMap.Builder<VariableDescriptor, NullabilityFlags> builder = ImmutableMap.builder();
builder.putAll(nullabilityInfo);
Map<VariableDescriptor, NullabilityFlags> builder = Maps.newHashMap(nullabilityInfo);
builder.put(variableDescriptor, new NullabilityFlags(!notNull, notNull));
return new DataFlowInfo(builder.build());
return new DataFlowInfo(ImmutableMap.copyOf(builder));
}
public DataFlowInfo and(DataFlowInfo other) {
Map<VariableDescriptor, NullabilityFlags> builder = Maps.newHashMap();
builder.putAll(nullabilityInfo);
for (Map.Entry<VariableDescriptor, NullabilityFlags> entry : other.nullabilityInfo.entrySet()) {
VariableDescriptor variableDescriptor = entry.getKey();
NullabilityFlags otherFlags = entry.getValue();
NullabilityFlags thisFlags = nullabilityInfo.get(variableDescriptor);
if (thisFlags != null) {
builder.put(variableDescriptor, thisFlags.and(otherFlags));
}
else {
builder.put(variableDescriptor, otherFlags);
}
}
return new DataFlowInfo(ImmutableMap.copyOf(builder));
}
public DataFlowInfo or(DataFlowInfo other) {
Map<VariableDescriptor, NullabilityFlags> builder = Maps.newHashMap(nullabilityInfo);
builder.keySet().retainAll(other.nullabilityInfo.keySet());
for (Map.Entry<VariableDescriptor, NullabilityFlags> entry : builder.entrySet()) {
VariableDescriptor variableDescriptor = entry.getKey();
NullabilityFlags thisFlags = entry.getValue();
NullabilityFlags otherFlags = other.nullabilityInfo.get(variableDescriptor);
assert (otherFlags != null);
builder.put(variableDescriptor, thisFlags.or(otherFlags));
}
return new DataFlowInfo(ImmutableMap.copyOf(builder));
}
private static class NullabilityFlags {
@@ -57,5 +109,14 @@ public class DataFlowInfo {
public boolean canBeNonNull() {
return canBeNonNull;
}
public NullabilityFlags and(NullabilityFlags other) {
return new NullabilityFlags(this.canBeNull && other.canBeNull, this.canBeNonNull && other.canBeNonNull);
}
public NullabilityFlags or(NullabilityFlags other) {
return new NullabilityFlags(this.canBeNull || other.canBeNull, this.canBeNonNull || other.canBeNonNull);
}
}
}
@@ -1323,52 +1323,66 @@ public class JetTypeInferrer {
}
}
private DataFlowInfo extractDataFlowInfoFromCondition(JetExpression condition, final boolean conditionValue) {
private DataFlowInfo extractDataFlowInfoFromCondition(@NotNull JetExpression condition, final boolean conditionValue) {
final DataFlowInfo[] result = new DataFlowInfo[] {dataFlowInfo};
condition.accept(new JetVisitor() {
@Override
public void visitBinaryExpression(JetBinaryExpression expression) {
IElementType operationToken = expression.getOperationToken();
if (operationToken != JetTokens.EQEQ
&& operationToken != JetTokens.EXCLEQ
&& operationToken != JetTokens.EQEQEQ
&& operationToken != JetTokens.EXCLEQEQEQ) {
return;
if (operationToken == JetTokens.ANDAND || operationToken == JetTokens.OROR) {
DataFlowInfo dataFlowInfo = extractDataFlowInfoFromCondition(expression.getLeft(), conditionValue);
JetExpression expressionRight = expression.getRight();
if (expressionRight != null) {
DataFlowInfo rightInfo = extractDataFlowInfoFromCondition(expressionRight, conditionValue);
DataFlowInfo.CompositionOperator operator;
if (operationToken == JetTokens.ANDAND) {
operator = conditionValue ? DataFlowInfo.AND : DataFlowInfo.OR;
}
else {
operator = conditionValue ? DataFlowInfo.OR : DataFlowInfo.AND;
}
dataFlowInfo = operator.compose(dataFlowInfo, rightInfo);
}
result[0] = dataFlowInfo;
}
else if (operationToken == JetTokens.EQEQ
|| operationToken == JetTokens.EXCLEQ
|| operationToken == JetTokens.EQEQEQ
|| operationToken == JetTokens.EXCLEQEQEQ) {
JetExpression left = expression.getLeft();
JetExpression right = expression.getRight();
if (left instanceof JetConstantExpression) {
JetExpression tmp = left;
left = right;
right = tmp;
}
JetExpression left = expression.getLeft();
JetExpression right = expression.getRight();
if (left instanceof JetConstantExpression) {
JetExpression tmp = left;
left = right;
right = tmp;
}
if (!(left instanceof JetSimpleNameExpression)) {
return;
}
JetSimpleNameExpression nameExpression = (JetSimpleNameExpression) left;
DeclarationDescriptor declarationDescriptor = trace.getBindingContext().resolveReferenceExpression(nameExpression);
if (!(declarationDescriptor instanceof VariableDescriptor)) {
return;
}
VariableDescriptor variableDescriptor = (VariableDescriptor) declarationDescriptor;
if (!(left instanceof JetSimpleNameExpression)) {
return;
}
JetSimpleNameExpression nameExpression = (JetSimpleNameExpression) left;
DeclarationDescriptor declarationDescriptor = trace.getBindingContext().resolveReferenceExpression(nameExpression);
if (!(declarationDescriptor instanceof VariableDescriptor)) {
return;
}
VariableDescriptor variableDescriptor = (VariableDescriptor) declarationDescriptor;
// TODO : validate that DF makes sense for this variable: local, val, internal w/backing field, etc
// TODO : validate that DF makes sense for this variable: local, val, internal w/backing field, etc
if (!(right instanceof JetConstantExpression)) {
return;
}
JetConstantExpression constantExpression = (JetConstantExpression) right;
if (constantExpression.getNode().getElementType() != JetNodeTypes.NULL) {
return;
}
if (!(right instanceof JetConstantExpression)) {
return;
}
JetConstantExpression constantExpression = (JetConstantExpression) right;
if (constantExpression.getNode().getElementType() != JetNodeTypes.NULL) {
return;
}
if (operationToken == JetTokens.EQEQ || operationToken == JetTokens.EQEQEQ) {
result[0] = dataFlowInfo.equalsToNull(variableDescriptor, !conditionValue);
}
else if (operationToken == JetTokens.EXCLEQ || operationToken == JetTokens.EXCLEQEQEQ) {
result[0] = dataFlowInfo.equalsToNull(variableDescriptor, conditionValue);
if (operationToken == JetTokens.EQEQ || operationToken == JetTokens.EQEQEQ) {
result[0] = dataFlowInfo.equalsToNull(variableDescriptor, !conditionValue);
}
else if (operationToken == JetTokens.EXCLEQ || operationToken == JetTokens.EXCLEQEQEQ) {
result[0] = dataFlowInfo.equalsToNull(variableDescriptor, conditionValue);
}
}
}
});
@@ -1567,11 +1581,11 @@ public class JetTypeInferrer {
}
if (selectorReturnType != null) {
// TODO : extensions to 'Any?'
assert selectorExpression != null;
if (selectorExpression != null) {
receiverType = enrichOutType(receiverExpression, receiverType);
receiverType = enrichOutType(receiverExpression, receiverType);
checkNullSafety(receiverType, expression.getOperationTokenNode(), getCalleeFunctionDescriptor(selectorExpression));
checkNullSafety(receiverType, expression.getOperationTokenNode(), getCalleeFunctionDescriptor(selectorExpression));
}
}
}
}
@@ -1648,7 +1662,7 @@ public class JetTypeInferrer {
// TODO : not a simple name -> resolve in scope, expect property type or a function type
trace.getErrorHandler().genericError(selectorExpression.getNode(), "Unsupported selector element type: " + selectorExpression);
}
return receiverType;
return null;
}
@Override
@@ -1790,7 +1804,8 @@ public class JetTypeInferrer {
}
else if (operationType == JetTokens.ANDAND || operationType == JetTokens.OROR) {
JetType leftType = getType(scope, left, false);
JetType rightType = right == null ? null : getType(scope, right, false);
DataFlowInfo flowInfoLeft = extractDataFlowInfoFromCondition(left, operationType == JetTokens.ANDAND); // TODO: This gets computed twice: here and in extractDataFlowInfoFromCondition() for the whole condition
JetType rightType = right == null ? null : getType(scope, right, false, flowInfoLeft);
if (leftType != null && !isBoolean(leftType)) {
trace.getErrorHandler().typeMismatch(left, semanticServices.getStandardLibrary().getBooleanType(), leftType);
}
+68
View File
@@ -0,0 +1,68 @@
fun test() {
val a : Int? = 0
if (a != null) {
a.plus(1)
}
else {
a?.plus(1)
}
val out : java.io.PrintStream? = null//= System.out
val ins = System.`in`
out?.println()
ins?.read()
if (ins != null) {
ins.read()
out?.println()
if (out != null) {
ins.read();
out.println();
}
}
if (out != null && ins != null) {
ins.read();
out.println();
}
if (out == null) {
out?.println()
} else {
out.println()
}
if (out != null && ins != null || out != null) {
ins?.read();
out.println();
}
if (out == null || out.println(0) == ()) {
out?.println(1)
}
else {
out.println(2)
}
if (out != null && out.println() == ()) {
out.println();
}
else {
out?.println();
}
if (out == null || out != null && out.println() == ()) {
out?.println();
}
else {
out.println();
}
if (1 == 2 || out != null && out.println(1) == ()) {
out?.println(2);
}
else {
out?.println(3)
}
}