diff --git a/.idea/projectCodeStyle.xml b/.idea/projectCodeStyle.xml
index 43813395c7f..cdd7d881640 100644
--- a/.idea/projectCodeStyle.xml
+++ b/.idea/projectCodeStyle.xml
@@ -127,17 +127,6 @@
-
-
-
-
-
-
-
-
-
-
-
diff --git a/idea/src/org/jetbrains/jet/lang/types/JetTypeInferrer.java b/idea/src/org/jetbrains/jet/lang/types/JetTypeInferrer.java
index dda2a3888b2..2bb01365b9e 100644
--- a/idea/src/org/jetbrains/jet/lang/types/JetTypeInferrer.java
+++ b/idea/src/org/jetbrains/jet/lang/types/JetTypeInferrer.java
@@ -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(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(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;
diff --git a/idea/src/org/jetbrains/jet/lang/types/TypeUtils.java b/idea/src/org/jetbrains/jet/lang/types/TypeUtils.java
index f0b75e08185..963c7757422 100644
--- a/idea/src/org/jetbrains/jet/lang/types/TypeUtils.java
+++ b/idea/src/org/jetbrains/jet/lang/types/TypeUtils.java
@@ -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;
}
}
diff --git a/idea/testData/checker/BinaryCallsOnNullableValues.jet b/idea/testData/checker/BinaryCallsOnNullableValues.jet
new file mode 100644
index 00000000000..8c7cda9e167
--- /dev/null
+++ b/idea/testData/checker/BinaryCallsOnNullableValues.jet
@@ -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 + 1
+ x plus 1
+ x < 1
+ x += 1
+
+ x == 1
+ x != 1
+
+ new A() == 1
+ new B() == 1
+ new C() == 1
+
+ x === "1"
+ x !== "1"
+
+ x === 1
+ x !== 1
+
+ x..2
+ x in 1..2
+
+ val y : Boolean? = true
+ false || y
+ y && true
+ y && 1
+}
\ No newline at end of file
diff --git a/idea/testData/resolve/ResolveOfInfixExpressions.jet b/idea/testData/resolve/ResolveOfInfixExpressions.jet
index 32bc20bbdde..0c072ad25ca 100644
--- a/idea/testData/resolve/ResolveOfInfixExpressions.jet
+++ b/idea/testData/resolve/ResolveOfInfixExpressions.jet
@@ -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>{}
diff --git a/idea/tests/org/jetbrains/jet/checkers/JetPsiCheckerTest.java b/idea/tests/org/jetbrains/jet/checkers/JetPsiCheckerTest.java
index ac6fe131e9d..40884999179 100644
--- a/idea/tests/org/jetbrains/jet/checkers/JetPsiCheckerTest.java
+++ b/idea/tests/org/jetbrains/jet/checkers/JetPsiCheckerTest.java
@@ -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);
+ }
}
diff --git a/idea/tests/org/jetbrains/jet/types/JetTypeCheckerTest.java b/idea/tests/org/jetbrains/jet/types/JetTypeCheckerTest.java
index 0c8f624f6c5..90043a5ef55 100644
--- a/idea/tests/org/jetbrains/jet/types/JetTypeCheckerTest.java
+++ b/idea/tests/org/jetbrains/jet/types/JetTypeCheckerTest.java
@@ -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", "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 typesToIntersect = new LinkedHashSet();
+ 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 subtypes = new ArrayList();
for (String type : types) {