Nullability checks for infix operations
This commit is contained in:
Generated
-11
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user