Nullability checks for infix operations

This commit is contained in:
Andrey Breslav
2011-03-28 18:17:57 +04:00
parent 739fce330c
commit 73bfc57492
7 changed files with 126 additions and 37 deletions
-11
View File
@@ -127,17 +127,6 @@
<option name="LABEL_INDENT_ABSOLUTE" value="false" />
<option name="USE_RELATIVE_INDENTS" value="false" />
</ADDITIONAL_INDENT_OPTIONS>
<codeStyleSettings language="JavaScript">
<option name="ELSE_ON_NEW_LINE" value="true" />
<option name="ALIGN_MULTILINE_BINARY_OPERATION" value="true" />
<option name="IF_BRACE_FORCE" value="1" />
<option name="PARENT_SETTINGS_INSTALLED" value="true" />
</codeStyleSettings>
<codeStyleSettings language="PHP">
<option name="ELSE_ON_NEW_LINE" value="true" />
<option name="IF_BRACE_FORCE" value="1" />
<option name="PARENT_SETTINGS_INSTALLED" value="true" />
</codeStyleSettings>
</value>
</option>
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
@@ -980,10 +980,26 @@ public class JetTypeInferrer {
}
else if (equalsOperations.contains(operationType)) {
String name = "equals";
JetType equalsType = getTypeForBinaryCall(expression, name, scope, true);
result = assureBooleanResult(operationSign, name, equalsType);
ensureNonemptyIntersectionOfOperandTypes(expression);
if (right != null) {
JetType leftType = getType(scope, left, false);
if (leftType != null) {
JetType rightType = getType(scope, right, false);
if (rightType != null) {
FunctionDescriptor equals = lookupFunction(
scope, operationSign, "equals",
leftType, Collections.singletonList(JetStandardClasses.getNullableAnyType()), false);
if (equals != null) {
if (ensureBooleanResult(operationSign, name, equals.getUnsubstitutedReturnType())) {
ensureNonemptyIntersectionOfOperandTypes(expression);
}
}
else {
semanticServices.getErrorHandler().genericError(operationSign.getNode(), "No method 'equals(Any?) : Boolean' available");
}
}
}
}
result = semanticServices.getStandardLibrary().getBooleanType();
}
else if (operationType == JetTokens.EQEQEQ || operationType == JetTokens.EXCLEQEQEQ) {
ensureNonemptyIntersectionOfOperandTypes(expression);
@@ -998,7 +1014,8 @@ public class JetTypeInferrer {
}
String name = "contains";
JetType containsType = getTypeForBinaryCall(scope, right, expression.getOperationReference(), expression.getLeft(), name, true);
result = assureBooleanResult(operationSign, name, containsType);
ensureBooleanResult(operationSign, name, containsType);
result = semanticServices.getStandardLibrary().getBooleanType();
}
else if (operationType == JetTokens.ANDAND || operationType == JetTokens.OROR) {
JetType leftType = getType(scope, left, false);
@@ -1007,7 +1024,6 @@ public class JetTypeInferrer {
semanticServices.getErrorHandler().typeMismatch(left, semanticServices.getStandardLibrary().getBooleanType(), leftType);
}
if (rightType != null && !isBoolean(rightType)) {
semanticServices.getErrorHandler().typeMismatch(left, semanticServices.getStandardLibrary().getBooleanType(), rightType);
semanticServices.getErrorHandler().typeMismatch(right, semanticServices.getStandardLibrary().getBooleanType(), rightType);
}
result = semanticServices.getStandardLibrary().getBooleanType();
@@ -1036,12 +1052,14 @@ public class JetTypeInferrer {
// TODO : duplicated effort for == and !=
JetType leftType = getType(scope, left, false);
if (right != null) {
if (leftType != null && right != null) {
JetType rightType = getType(scope, right, false);
JetType intersect = TypeUtils.intersect(semanticServices.getTypeChecker(), new HashSet<JetType>(Arrays.asList(leftType, rightType)));
if (intersect == null) {
semanticServices.getErrorHandler().genericError(expression.getNode(), "Operator " + operationSign.getReferencedName() + " cannot be applied to " + leftType + " and " + rightType);
if (rightType != null) {
JetType intersect = TypeUtils.intersect(semanticServices.getTypeChecker(), new HashSet<JetType>(Arrays.asList(leftType, rightType)));
if (intersect == null) {
semanticServices.getErrorHandler().genericError(expression.getNode(), "Operator " + operationSign.getReferencedName() + " cannot be applied to " + leftType + " and " + rightType);
}
}
}
}
@@ -1058,20 +1076,19 @@ public class JetTypeInferrer {
semanticServices.getErrorHandler().genericError(expression.getNode(), "Assignments are not expressions, and only expressions are allowed in this context");
}
private JetType assureBooleanResult(JetSimpleNameExpression operationSign, String name, JetType resultType) {
private boolean ensureBooleanResult(JetSimpleNameExpression operationSign, String name, JetType resultType) {
if (resultType != null) {
// TODO : Relax?
if (!isBoolean(resultType)) {
semanticServices.getErrorHandler().genericError(operationSign.getNode(), "'" + name + "' must return Boolean but returns " + resultType);
return null;
} else {
return resultType;
return false;
}
}
return resultType;
return true;
}
private boolean isBoolean(@NotNull JetType type) {
if (type.isNullable()) return false;
TypeConstructor booleanTypeConstructor = semanticServices.getStandardLibrary().getBoolean().getTypeConstructor();
return type.getConstructor().equals(booleanTypeConstructor) || ErrorUtils.isErrorType(type);
}
@@ -1093,7 +1110,11 @@ public class JetTypeInferrer {
}
@Nullable
protected JetType getTypeForBinaryCall(JetBinaryExpression expression, @NotNull String name, JetScope scope, boolean reportUnresolved) {
protected JetType getTypeForBinaryCall(
@NotNull JetBinaryExpression expression,
@NotNull String name,
@NotNull JetScope scope,
boolean reportUnresolved) {
JetExpression left = expression.getLeft();
JetExpression right = expression.getRight();
if (right == null) {
@@ -1104,14 +1125,29 @@ public class JetTypeInferrer {
}
@Nullable
private JetType getTypeForBinaryCall(JetScope scope, JetExpression left, JetSimpleNameExpression operationSign, @NotNull JetExpression right, String name, boolean reportUnresolved) {
JetType leftType = safeGetType(scope, left, false);
JetType rightType = safeGetType(scope, right, false);
if (ErrorUtils.isErrorType(leftType)) {
private JetType getTypeForBinaryCall(
@NotNull JetScope scope,
@NotNull JetExpression left,
@NotNull JetSimpleNameExpression operationSign,
@NotNull JetExpression right,
@NotNull String name,
boolean reportUnresolved) {
JetType leftType = getType(scope, left, false);
JetType rightType = getType(scope, right, false);
if (leftType == null || rightType == null) {
return null;
}
FunctionDescriptor functionDescriptor = lookupFunction(scope, operationSign, name, leftType, Collections.singletonList(rightType), reportUnresolved);
if (functionDescriptor != null) {
if (leftType.isNullable()) {
// TODO : better error message for '1 + nullableVar' case
semanticServices.getErrorHandler().genericError(operationSign.getNode(),
"Infix call corresponds to a dot-qualified call '" +
left.getText() + "." + name + "(" + right.getText() + ")'" +
" which is not allowed on a nullable receiver '" + right.getText() + "'." +
" Use '?.'-qualified call instead");
}
return functionDescriptor.getUnsubstitutedReturnType();
}
return null;
@@ -40,7 +40,7 @@ public class TypeUtils {
if (!canHaveSubtypes(typeChecker, type)) {
for (JetType other : types) {
if (type != other || !typeChecker.isSubtypeOf(type, other)) {
if (!type.equals(other) && !typeChecker.isSubtypeOf(type, other)) {
return null;
}
}
@@ -0,0 +1,41 @@
class A {
fun equals(a : Any?) : Boolean
}
class B {
fun equals(a : Any?) : Boolean?
}
class C {
fun equals(a : Any?) : Int
}
fun f(): Int {
var x: Int? = 1
x = 1
x <error>+</error> 1
x <error>plus</error> 1
x <error><</error> 1
x <error>+=</error> 1
x == 1
x != 1
<error>new A() == 1</error>
new B() <error>==</error> 1
new C() <error>==</error> 1
<error>x === "1"</error>
<error>x !== "1"</error>
x === 1
x !== 1
x<error>..</error>2
x <error>in</error> 1..2
val y : Boolean? = true
false || <error>y</error>
<error>y</error> && true
<error>y</error> && <error>1</error>
}
@@ -1,6 +1,12 @@
import java.*
import util.*
fun f_plus(): Int {
var x: Int = 1
x = x `std::Int.plus(Int)`+ 1
return x
}
~X~class X<~T~T> {
~X()~this() {}
fun foo(a : `T`T) : `X`X<`T`T>{}
@@ -20,7 +20,11 @@ public class JetPsiCheckerTest extends LightDaemonAnalyzerTestCase {
return new File(PathManager.getResourceRoot(JetParsingTest.class, "/org/jetbrains/jet/parsing/JetParsingTest.class")).getParentFile().getParentFile().getParent();
}
public void testFoo() throws Exception {
public void testProperties() throws Exception {
doTest("/checker/Properties.jet", true, true);
}
public void testBinaryCallsOnNullableValues() throws Exception {
doTest("/checker/BinaryCallsOnNullableValues.jet", true, true);
}
}
@@ -14,10 +14,7 @@ import org.jetbrains.jet.lang.types.*;
import org.jetbrains.jet.parsing.JetParsingTest;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.*;
/**
* @author abreslav
@@ -149,6 +146,12 @@ public class JetTypeCheckerTest extends LightDaemonAnalyzerTestCase {
assertCommonSupertype("Base_T<*>", "Base_T<Int>", "Base_T<*>");
}
public void testIntersect() throws Exception {
assertIntersection("Int?", "Int?", "Int?");
assertIntersection("Int", "Int?", "Int");
assertIntersection("Int", "Int", "Int?");
}
public void testBasicSubtyping() throws Exception {
assertSubtype("Boolean", "Boolean");
assertSubtype("Byte", "Byte");
@@ -420,6 +423,16 @@ public class JetTypeCheckerTest extends LightDaemonAnalyzerTestCase {
assertSubtypingRelation(type1, type2, false);
}
private void assertIntersection(String expected, String... types) {
Set<JetType> typesToIntersect = new LinkedHashSet<JetType>();
for (String type : types) {
typesToIntersect.add(makeType(type));
}
JetType result = TypeUtils.intersect(semanticServices.getTypeChecker(), typesToIntersect);
// assertNotNull("Intersection is null for " + typesToIntersect, result);
assertEquals(makeType(expected), result);
}
private void assertCommonSupertype(String expected, String... types) {
Collection<JetType> subtypes = new ArrayList<JetType>();
for (String type : types) {