Nullability analysis supported for simple cases
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user