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="LABEL_INDENT_ABSOLUTE" value="false" />
|
||||||
<option name="USE_RELATIVE_INDENTS" value="false" />
|
<option name="USE_RELATIVE_INDENTS" value="false" />
|
||||||
</ADDITIONAL_INDENT_OPTIONS>
|
</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>
|
</value>
|
||||||
</option>
|
</option>
|
||||||
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
|
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
|
||||||
|
|||||||
@@ -980,10 +980,26 @@ public class JetTypeInferrer {
|
|||||||
}
|
}
|
||||||
else if (equalsOperations.contains(operationType)) {
|
else if (equalsOperations.contains(operationType)) {
|
||||||
String name = "equals";
|
String name = "equals";
|
||||||
JetType equalsType = getTypeForBinaryCall(expression, name, scope, true);
|
if (right != null) {
|
||||||
result = assureBooleanResult(operationSign, name, equalsType);
|
JetType leftType = getType(scope, left, false);
|
||||||
|
if (leftType != null) {
|
||||||
ensureNonemptyIntersectionOfOperandTypes(expression);
|
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) {
|
else if (operationType == JetTokens.EQEQEQ || operationType == JetTokens.EXCLEQEQEQ) {
|
||||||
ensureNonemptyIntersectionOfOperandTypes(expression);
|
ensureNonemptyIntersectionOfOperandTypes(expression);
|
||||||
@@ -998,7 +1014,8 @@ public class JetTypeInferrer {
|
|||||||
}
|
}
|
||||||
String name = "contains";
|
String name = "contains";
|
||||||
JetType containsType = getTypeForBinaryCall(scope, right, expression.getOperationReference(), expression.getLeft(), name, true);
|
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) {
|
else if (operationType == JetTokens.ANDAND || operationType == JetTokens.OROR) {
|
||||||
JetType leftType = getType(scope, left, false);
|
JetType leftType = getType(scope, left, false);
|
||||||
@@ -1007,7 +1024,6 @@ public class JetTypeInferrer {
|
|||||||
semanticServices.getErrorHandler().typeMismatch(left, semanticServices.getStandardLibrary().getBooleanType(), leftType);
|
semanticServices.getErrorHandler().typeMismatch(left, semanticServices.getStandardLibrary().getBooleanType(), leftType);
|
||||||
}
|
}
|
||||||
if (rightType != null && !isBoolean(rightType)) {
|
if (rightType != null && !isBoolean(rightType)) {
|
||||||
semanticServices.getErrorHandler().typeMismatch(left, semanticServices.getStandardLibrary().getBooleanType(), rightType);
|
|
||||||
semanticServices.getErrorHandler().typeMismatch(right, semanticServices.getStandardLibrary().getBooleanType(), rightType);
|
semanticServices.getErrorHandler().typeMismatch(right, semanticServices.getStandardLibrary().getBooleanType(), rightType);
|
||||||
}
|
}
|
||||||
result = semanticServices.getStandardLibrary().getBooleanType();
|
result = semanticServices.getStandardLibrary().getBooleanType();
|
||||||
@@ -1036,12 +1052,14 @@ public class JetTypeInferrer {
|
|||||||
|
|
||||||
// TODO : duplicated effort for == and !=
|
// TODO : duplicated effort for == and !=
|
||||||
JetType leftType = getType(scope, left, false);
|
JetType leftType = getType(scope, left, false);
|
||||||
if (right != null) {
|
if (leftType != null && right != null) {
|
||||||
JetType rightType = getType(scope, right, false);
|
JetType rightType = getType(scope, right, false);
|
||||||
|
|
||||||
JetType intersect = TypeUtils.intersect(semanticServices.getTypeChecker(), new HashSet<JetType>(Arrays.asList(leftType, rightType)));
|
if (rightType != null) {
|
||||||
if (intersect == null) {
|
JetType intersect = TypeUtils.intersect(semanticServices.getTypeChecker(), new HashSet<JetType>(Arrays.asList(leftType, rightType)));
|
||||||
semanticServices.getErrorHandler().genericError(expression.getNode(), "Operator " + operationSign.getReferencedName() + " cannot be applied to " + leftType + " and " + 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");
|
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) {
|
if (resultType != null) {
|
||||||
// TODO : Relax?
|
// TODO : Relax?
|
||||||
if (!isBoolean(resultType)) {
|
if (!isBoolean(resultType)) {
|
||||||
semanticServices.getErrorHandler().genericError(operationSign.getNode(), "'" + name + "' must return Boolean but returns " + resultType);
|
semanticServices.getErrorHandler().genericError(operationSign.getNode(), "'" + name + "' must return Boolean but returns " + resultType);
|
||||||
return null;
|
return false;
|
||||||
} else {
|
|
||||||
return resultType;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return resultType;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isBoolean(@NotNull JetType type) {
|
private boolean isBoolean(@NotNull JetType type) {
|
||||||
|
if (type.isNullable()) return false;
|
||||||
TypeConstructor booleanTypeConstructor = semanticServices.getStandardLibrary().getBoolean().getTypeConstructor();
|
TypeConstructor booleanTypeConstructor = semanticServices.getStandardLibrary().getBoolean().getTypeConstructor();
|
||||||
return type.getConstructor().equals(booleanTypeConstructor) || ErrorUtils.isErrorType(type);
|
return type.getConstructor().equals(booleanTypeConstructor) || ErrorUtils.isErrorType(type);
|
||||||
}
|
}
|
||||||
@@ -1093,7 +1110,11 @@ public class JetTypeInferrer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@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 left = expression.getLeft();
|
||||||
JetExpression right = expression.getRight();
|
JetExpression right = expression.getRight();
|
||||||
if (right == null) {
|
if (right == null) {
|
||||||
@@ -1104,14 +1125,29 @@ public class JetTypeInferrer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private JetType getTypeForBinaryCall(JetScope scope, JetExpression left, JetSimpleNameExpression operationSign, @NotNull JetExpression right, String name, boolean reportUnresolved) {
|
private JetType getTypeForBinaryCall(
|
||||||
JetType leftType = safeGetType(scope, left, false);
|
@NotNull JetScope scope,
|
||||||
JetType rightType = safeGetType(scope, right, false);
|
@NotNull JetExpression left,
|
||||||
if (ErrorUtils.isErrorType(leftType)) {
|
@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;
|
return null;
|
||||||
}
|
}
|
||||||
FunctionDescriptor functionDescriptor = lookupFunction(scope, operationSign, name, leftType, Collections.singletonList(rightType), reportUnresolved);
|
FunctionDescriptor functionDescriptor = lookupFunction(scope, operationSign, name, leftType, Collections.singletonList(rightType), reportUnresolved);
|
||||||
if (functionDescriptor != null) {
|
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 functionDescriptor.getUnsubstitutedReturnType();
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ public class TypeUtils {
|
|||||||
|
|
||||||
if (!canHaveSubtypes(typeChecker, type)) {
|
if (!canHaveSubtypes(typeChecker, type)) {
|
||||||
for (JetType other : types) {
|
for (JetType other : types) {
|
||||||
if (type != other || !typeChecker.isSubtypeOf(type, other)) {
|
if (!type.equals(other) && !typeChecker.isSubtypeOf(type, other)) {
|
||||||
return null;
|
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 java.*
|
||||||
import util.*
|
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~class X<~T~T> {
|
||||||
~X()~this() {}
|
~X()~this() {}
|
||||||
fun foo(a : `T`T) : `X`X<`T`T>{}
|
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();
|
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);
|
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 org.jetbrains.jet.parsing.JetParsingTest;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.ArrayList;
|
import java.util.*;
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author abreslav
|
* @author abreslav
|
||||||
@@ -149,6 +146,12 @@ public class JetTypeCheckerTest extends LightDaemonAnalyzerTestCase {
|
|||||||
assertCommonSupertype("Base_T<*>", "Base_T<Int>", "Base_T<*>");
|
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 {
|
public void testBasicSubtyping() throws Exception {
|
||||||
assertSubtype("Boolean", "Boolean");
|
assertSubtype("Boolean", "Boolean");
|
||||||
assertSubtype("Byte", "Byte");
|
assertSubtype("Byte", "Byte");
|
||||||
@@ -420,6 +423,16 @@ public class JetTypeCheckerTest extends LightDaemonAnalyzerTestCase {
|
|||||||
assertSubtypingRelation(type1, type2, false);
|
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) {
|
private void assertCommonSupertype(String expected, String... types) {
|
||||||
Collection<JetType> subtypes = new ArrayList<JetType>();
|
Collection<JetType> subtypes = new ArrayList<JetType>();
|
||||||
for (String type : types) {
|
for (String type : types) {
|
||||||
|
|||||||
Reference in New Issue
Block a user