Nullability analysis supported for simple cases
This commit is contained in:
@@ -1,10 +1,13 @@
|
|||||||
package org.jetbrains.jet.lang.types;
|
package org.jetbrains.jet.lang.types;
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableMap;
|
import com.google.common.collect.ImmutableMap;
|
||||||
|
import com.google.common.collect.Maps;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
import org.jetbrains.jet.lang.descriptors.VariableDescriptor;
|
import org.jetbrains.jet.lang.descriptors.VariableDescriptor;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author abreslav
|
* @author abreslav
|
||||||
*/
|
*/
|
||||||
@@ -16,6 +19,26 @@ public class DataFlowInfo {
|
|||||||
return EMPTY;
|
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;
|
private final ImmutableMap<VariableDescriptor, NullabilityFlags> nullabilityInfo;
|
||||||
|
|
||||||
public DataFlowInfo(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) {
|
public DataFlowInfo equalsToNull(@NotNull VariableDescriptor variableDescriptor, boolean notNull) {
|
||||||
ImmutableMap.Builder<VariableDescriptor, NullabilityFlags> builder = ImmutableMap.builder();
|
Map<VariableDescriptor, NullabilityFlags> builder = Maps.newHashMap(nullabilityInfo);
|
||||||
builder.putAll(nullabilityInfo);
|
|
||||||
builder.put(variableDescriptor, new NullabilityFlags(!notNull, notNull));
|
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 {
|
private static class NullabilityFlags {
|
||||||
@@ -57,5 +109,14 @@ public class DataFlowInfo {
|
|||||||
public boolean canBeNonNull() {
|
public boolean canBeNonNull() {
|
||||||
return 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};
|
final DataFlowInfo[] result = new DataFlowInfo[] {dataFlowInfo};
|
||||||
condition.accept(new JetVisitor() {
|
condition.accept(new JetVisitor() {
|
||||||
@Override
|
@Override
|
||||||
public void visitBinaryExpression(JetBinaryExpression expression) {
|
public void visitBinaryExpression(JetBinaryExpression expression) {
|
||||||
IElementType operationToken = expression.getOperationToken();
|
IElementType operationToken = expression.getOperationToken();
|
||||||
if (operationToken != JetTokens.EQEQ
|
if (operationToken == JetTokens.ANDAND || operationToken == JetTokens.OROR) {
|
||||||
&& operationToken != JetTokens.EXCLEQ
|
DataFlowInfo dataFlowInfo = extractDataFlowInfoFromCondition(expression.getLeft(), conditionValue);
|
||||||
&& operationToken != JetTokens.EQEQEQ
|
JetExpression expressionRight = expression.getRight();
|
||||||
&& operationToken != JetTokens.EXCLEQEQEQ) {
|
if (expressionRight != null) {
|
||||||
return;
|
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();
|
if (!(left instanceof JetSimpleNameExpression)) {
|
||||||
JetExpression right = expression.getRight();
|
return;
|
||||||
if (left instanceof JetConstantExpression) {
|
}
|
||||||
JetExpression tmp = left;
|
JetSimpleNameExpression nameExpression = (JetSimpleNameExpression) left;
|
||||||
left = right;
|
DeclarationDescriptor declarationDescriptor = trace.getBindingContext().resolveReferenceExpression(nameExpression);
|
||||||
right = tmp;
|
if (!(declarationDescriptor instanceof VariableDescriptor)) {
|
||||||
}
|
return;
|
||||||
|
}
|
||||||
|
VariableDescriptor variableDescriptor = (VariableDescriptor) declarationDescriptor;
|
||||||
|
|
||||||
if (!(left instanceof JetSimpleNameExpression)) {
|
// TODO : validate that DF makes sense for this variable: local, val, internal w/backing field, etc
|
||||||
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
|
if (!(right instanceof JetConstantExpression)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
JetConstantExpression constantExpression = (JetConstantExpression) right;
|
||||||
|
if (constantExpression.getNode().getElementType() != JetNodeTypes.NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!(right instanceof JetConstantExpression)) {
|
if (operationToken == JetTokens.EQEQ || operationToken == JetTokens.EQEQEQ) {
|
||||||
return;
|
result[0] = dataFlowInfo.equalsToNull(variableDescriptor, !conditionValue);
|
||||||
}
|
}
|
||||||
JetConstantExpression constantExpression = (JetConstantExpression) right;
|
else if (operationToken == JetTokens.EXCLEQ || operationToken == JetTokens.EXCLEQEQEQ) {
|
||||||
if (constantExpression.getNode().getElementType() != JetNodeTypes.NULL) {
|
result[0] = dataFlowInfo.equalsToNull(variableDescriptor, conditionValue);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -1567,11 +1581,11 @@ public class JetTypeInferrer {
|
|||||||
}
|
}
|
||||||
if (selectorReturnType != null) {
|
if (selectorReturnType != null) {
|
||||||
// TODO : extensions to 'Any?'
|
// 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
|
// 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);
|
trace.getErrorHandler().genericError(selectorExpression.getNode(), "Unsupported selector element type: " + selectorExpression);
|
||||||
}
|
}
|
||||||
return receiverType;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -1790,7 +1804,8 @@ public class JetTypeInferrer {
|
|||||||
}
|
}
|
||||||
else if (operationType == JetTokens.ANDAND || operationType == JetTokens.OROR) {
|
else if (operationType == JetTokens.ANDAND || operationType == JetTokens.OROR) {
|
||||||
JetType leftType = getType(scope, left, false);
|
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)) {
|
if (leftType != null && !isBoolean(leftType)) {
|
||||||
trace.getErrorHandler().typeMismatch(left, semanticServices.getStandardLibrary().getBooleanType(), 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