diff --git a/idea/src/org/jetbrains/jet/lang/types/DataFlowInfo.java b/idea/src/org/jetbrains/jet/lang/types/DataFlowInfo.java index 6f826169974..761abb986f9 100644 --- a/idea/src/org/jetbrains/jet/lang/types/DataFlowInfo.java +++ b/idea/src/org/jetbrains/jet/lang/types/DataFlowInfo.java @@ -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 nullabilityInfo; public DataFlowInfo(ImmutableMap nullabilityInfo) { @@ -35,10 +58,39 @@ public class DataFlowInfo { } public DataFlowInfo equalsToNull(@NotNull VariableDescriptor variableDescriptor, boolean notNull) { - ImmutableMap.Builder builder = ImmutableMap.builder(); - builder.putAll(nullabilityInfo); + Map 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 builder = Maps.newHashMap(); + builder.putAll(nullabilityInfo); + for (Map.Entry 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 builder = Maps.newHashMap(nullabilityInfo); + builder.keySet().retainAll(other.nullabilityInfo.keySet()); + for (Map.Entry 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); + } + } } diff --git a/idea/src/org/jetbrains/jet/lang/types/JetTypeInferrer.java b/idea/src/org/jetbrains/jet/lang/types/JetTypeInferrer.java index e6a4d83d4e3..f1ce555fc02 100644 --- a/idea/src/org/jetbrains/jet/lang/types/JetTypeInferrer.java +++ b/idea/src/org/jetbrains/jet/lang/types/JetTypeInferrer.java @@ -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); } diff --git a/idea/testData/checker/Nullability.jet b/idea/testData/checker/Nullability.jet new file mode 100644 index 00000000000..2d4603c14e0 --- /dev/null +++ b/idea/testData/checker/Nullability.jet @@ -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) + } +}