diff --git a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java index 5261a4f7552..8ff1dfe598b 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java @@ -640,6 +640,9 @@ public interface Errors { // Conventions + DiagnosticFactory2 DEPRECATED_IDENTITY_EQUALS = DiagnosticFactory2.create(WARNING); + DiagnosticFactory2 IMPLICIT_BOXING_IN_IDENTITY_EQUALS = DiagnosticFactory2.create(WARNING); + DiagnosticFactory2 DEPRECATED_BINARY_MOD = DiagnosticFactory2.create(WARNING); DiagnosticFactory2 DEPRECATED_BINARY_MOD_AS_REM = DiagnosticFactory2.create(WARNING); diff --git a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java index 6fef4e8e2fd..74a0ce2fa1f 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java @@ -383,6 +383,9 @@ public class DefaultErrorMessages { MAP.put(LOCAL_VARIABLE_WITH_SETTER, "Local variables are not allowed to have setters"); MAP.put(VAL_WITH_SETTER, "A 'val'-property cannot have a setter"); + MAP.put(DEPRECATED_IDENTITY_EQUALS, "Identity equality for arguments of types {0} and {1} is deprecated", RENDER_TYPE, RENDER_TYPE); + MAP.put(IMPLICIT_BOXING_IN_IDENTITY_EQUALS, "Identity equality for arguments of types {0} and {1} can be unstable because of implicit boxing", RENDER_TYPE, RENDER_TYPE); + MAP.put(DEPRECATED_BINARY_MOD, "Deprecated convention for ''{0}''. Use ''{1}''", NAME, STRING); MAP.put(DEPRECATED_BINARY_MOD_AS_REM, "''%'' is resolved to deprecated ''{0}'' operator. Replace with ''.{0}'' or add operator ''{1}''", NAME, STRING); diff --git a/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/BasicExpressionTypingVisitor.java b/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/BasicExpressionTypingVisitor.java index 49c7344f158..d37723be6e8 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/BasicExpressionTypingVisitor.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/BasicExpressionTypingVisitor.java @@ -1098,6 +1098,7 @@ public class BasicExpressionTypingVisitor extends ExpressionTypingVisitor { ensureNonemptyIntersectionOfOperandTypes(expression, context); // TODO : Check comparison pointlessness result = TypeInfoFactoryKt.createTypeInfo(components.builtIns.getBooleanType(), context); + checkIdentityOnPrimitiveTypes(expression, context); } else if (OperatorConventions.IN_OPERATIONS.contains(operationType)) { ValueArgument leftArgument = CallMaker.makeValueArgument(left, left != null ? left : operationSign); @@ -1119,6 +1120,27 @@ public class BasicExpressionTypingVisitor extends ExpressionTypingVisitor { return components.dataFlowAnalyzer.checkType(result, expression, contextWithExpectedType); } + private static void checkIdentityOnPrimitiveTypes(@NotNull KtBinaryExpression expression, @NotNull ExpressionTypingContext context) { + if (expression.getLeft() == null || expression.getRight() == null) return; + + KotlinType leftType = context.trace.getType(expression.getLeft()); + KotlinType rightType = context.trace.getType(expression.getRight()); + if (leftType == null || rightType == null) return; + + if (KotlinTypeChecker.DEFAULT.equalTypes(leftType, rightType) && KotlinBuiltIns.isPrimitiveType(leftType)) { + context.trace.report(DEPRECATED_IDENTITY_EQUALS.on(expression, leftType, rightType)); + } + else if (isIdentityComparedWithImplicitBoxing(leftType, rightType) || isIdentityComparedWithImplicitBoxing(rightType, leftType)) { + context.trace.report(IMPLICIT_BOXING_IN_IDENTITY_EQUALS.on(expression, leftType, rightType)); + } + } + + private static boolean isIdentityComparedWithImplicitBoxing(KotlinType leftType, KotlinType rightType) { + return KotlinBuiltIns.isPrimitiveType(leftType) && + !KotlinBuiltIns.isPrimitiveType(rightType) && + KotlinTypeChecker.DEFAULT.isSubtypeOf(leftType, rightType); + } + private KotlinTypeInfo visitEquality( KtBinaryExpression expression, ExpressionTypingContext context, diff --git a/compiler/testData/diagnostics/tests/BinaryCallsOnNullableValues.kt b/compiler/testData/diagnostics/tests/BinaryCallsOnNullableValues.kt index 49502e607d4..4cdef06567c 100644 --- a/compiler/testData/diagnostics/tests/BinaryCallsOnNullableValues.kt +++ b/compiler/testData/diagnostics/tests/BinaryCallsOnNullableValues.kt @@ -18,8 +18,8 @@ fun f(): Unit { x === "1" x !== "1" - x === 1 - x !== 1 + x === 1 + x !== 1 x..2 x in 1..2 diff --git a/compiler/testData/diagnostics/tests/IdentityComparisonWithPrimitives.kt b/compiler/testData/diagnostics/tests/IdentityComparisonWithPrimitives.kt new file mode 100644 index 00000000000..05b44659533 --- /dev/null +++ b/compiler/testData/diagnostics/tests/IdentityComparisonWithPrimitives.kt @@ -0,0 +1,81 @@ +val z: Boolean = true +val b: Byte = 0 +val s: Short = 0 +val i: Int = 0 +val j: Long = 0L +val f: Float = 0.0f +val d: Double = 0.0 +val c: Char = '0' + +val nz: Boolean? = true +val nb: Byte? = 0 +val ns: Short? = 0 +val ni: Int? = 0 +val nj: Long? = 0L +val nf: Float? = 0.0f +val nd: Double? = 0.0 +val nc: Char? = '0' + +val n: Number = 0 +val nn: Number? = 0 +val a: Any = 0 +val na: Any? = 0 + +// Identity for primitive values of same type +val test_zz = z === z || z !== z +val test_bb = b === b || b !== b +val test_ss = s === s || s !== s +val test_ii = i === i || i !== i +val test_jj = j === j || j !== j +val test_ff = f === f || f !== f +val test_dd = d === d || d !== d +val test_cc = c === c || c !== c + +// Identity for primitive values of different types (no extra error) +val test_zb = z === b || z !== b + +// Primitive vs nullable +val test_znz = z === nz || nz === z || z !== nz || nz !== z +val test_bnb = b === nb || nb === b || b !== nb || nb !== b +val test_sns = s === ns || ns === s || s !== ns || ns !== s +val test_ini = i === ni || ni === i || i !== ni || ni !== i +val test_jnj = j === nj || nj === j || j !== nj || nj !== j +val test_fnf = f === nf || nf === f || f !== nf || nf !== f +val test_dnd = d === nd || nd === d || d !== nd || nd !== d +val test_cnc = c === nc || nc === c || c !== nc || nc !== c + +// Primitive number vs Number +val test_bn = b === n || n === b || b !== n || n !== b +val test_sn = s === n || n === s || s !== n || n !== s +val test_in = i === n || n === i || i !== n || n !== i +val test_jn = j === n || n === j || j !== n || n !== j +val test_fn = f === n || n === f || f !== n || n !== f +val test_dn = d === n || n === d || d !== n || n !== d + +// Primitive number vs Number? +val test_bnn = b === nn || nn === b || b !== nn || nn !== b +val test_snn = s === nn || nn === s || s !== nn || nn !== s +val test_inn = i === nn || nn === i || i !== nn || nn !== i +val test_jnn = j === nn || nn === j || j !== nn || nn !== j +val test_fnn = f === nn || nn === f || f !== nn || nn !== f +val test_dnn = d === nn || nn === d || d !== nn || nn !== d + +// Primitive vs Any +val test_za = z === a || a === z || z !== a || a !== z +val test_ba = b === a || a === b || b !== a || a !== b +val test_sa = s === a || a === s || s !== a || a !== s +val test_ia = i === a || a === i || i !== a || a !== i +val test_ja = j === a || a === j || j !== a || a !== j +val test_fa = f === a || a === f || f !== a || a !== f +val test_da = d === a || a === d || d !== a || a !== d +val test_ca = c === a || a === c || c !== a || a !== c + +// Primitive vs Any? +val test_zna = z === na || na === z || z !== na || na !== z +val test_bna = b === na || na === b || b !== na || na !== b +val test_sna = s === na || na === s || s !== na || na !== s +val test_ina = i === na || na === i || i !== na || na !== i +val test_jna = j === na || na === j || j !== na || na !== j +val test_fna = f === na || na === f || f !== na || na !== f +val test_dna = d === na || na === d || d !== na || na !== d +val test_cna = c === na || na === c || c !== na || na !== c \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/IdentityComparisonWithPrimitives.txt b/compiler/testData/diagnostics/tests/IdentityComparisonWithPrimitives.txt new file mode 100644 index 00000000000..8db2fbc239d --- /dev/null +++ b/compiler/testData/diagnostics/tests/IdentityComparisonWithPrimitives.txt @@ -0,0 +1,67 @@ +package + +public val a: kotlin.Any = 0 +public val b: kotlin.Byte = 0.toByte() +public val c: kotlin.Char = \u0030 ('0') +public val d: kotlin.Double = 0.0.toDouble() +public val f: kotlin.Float = 0.0.toFloat() +public val i: kotlin.Int = 0 +public val j: kotlin.Long = 0.toLong() +public val n: kotlin.Number = 0 +public val na: kotlin.Any? = 0 +public val nb: kotlin.Byte? = 0.toByte() +public val nc: kotlin.Char? = \u0030 ('0') +public val nd: kotlin.Double? = 0.0.toDouble() +public val nf: kotlin.Float? = 0.0.toFloat() +public val ni: kotlin.Int? = 0 +public val nj: kotlin.Long? = 0.toLong() +public val nn: kotlin.Number? = 0 +public val ns: kotlin.Short? = 0.toShort() +public val nz: kotlin.Boolean? = true +public val s: kotlin.Short = 0.toShort() +public val test_ba: kotlin.Boolean +public val test_bb: kotlin.Boolean +public val test_bn: kotlin.Boolean +public val test_bna: kotlin.Boolean +public val test_bnb: kotlin.Boolean +public val test_bnn: kotlin.Boolean +public val test_ca: kotlin.Boolean +public val test_cc: kotlin.Boolean +public val test_cna: kotlin.Boolean +public val test_cnc: kotlin.Boolean +public val test_da: kotlin.Boolean +public val test_dd: kotlin.Boolean +public val test_dn: kotlin.Boolean +public val test_dna: kotlin.Boolean +public val test_dnd: kotlin.Boolean +public val test_dnn: kotlin.Boolean +public val test_fa: kotlin.Boolean +public val test_ff: kotlin.Boolean +public val test_fn: kotlin.Boolean +public val test_fna: kotlin.Boolean +public val test_fnf: kotlin.Boolean +public val test_fnn: kotlin.Boolean +public val test_ia: kotlin.Boolean +public val test_ii: kotlin.Boolean +public val test_in: kotlin.Boolean +public val test_ina: kotlin.Boolean +public val test_ini: kotlin.Boolean +public val test_inn: kotlin.Boolean +public val test_ja: kotlin.Boolean +public val test_jj: kotlin.Boolean +public val test_jn: kotlin.Boolean +public val test_jna: kotlin.Boolean +public val test_jnj: kotlin.Boolean +public val test_jnn: kotlin.Boolean +public val test_sa: kotlin.Boolean +public val test_sn: kotlin.Boolean +public val test_sna: kotlin.Boolean +public val test_snn: kotlin.Boolean +public val test_sns: kotlin.Boolean +public val test_ss: kotlin.Boolean +public val test_za: kotlin.Boolean +public val test_zb: kotlin.Boolean +public val test_zna: kotlin.Boolean +public val test_znz: kotlin.Boolean +public val test_zz: kotlin.Boolean +public val z: kotlin.Boolean = true diff --git a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java index 6e25e1467b2..39bb6106f83 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java @@ -302,6 +302,12 @@ public class DiagnosticsTestGenerated extends AbstractDiagnosticsTest { doTest(fileName); } + @TestMetadata("IdentityComparisonWithPrimitives.kt") + public void testIdentityComparisonWithPrimitives() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/IdentityComparisonWithPrimitives.kt"); + doTest(fileName); + } + @TestMetadata("implicitIntersection.kt") public void testImplicitIntersection() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/implicitIntersection.kt"); diff --git a/idea/testData/checker/BinaryCallsOnNullableValues.kt b/idea/testData/checker/BinaryCallsOnNullableValues.kt index 825c8769df2..53605fa5e04 100644 --- a/idea/testData/checker/BinaryCallsOnNullableValues.kt +++ b/idea/testData/checker/BinaryCallsOnNullableValues.kt @@ -18,8 +18,8 @@ fun f(): Unit { x === "1" x !== "1" - x === 1 - x !== 1 + x === 1 + x !== 1 x..2 x in 1..2