diff --git a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java index d420bae26de..8f6b0f9cf26 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java @@ -744,6 +744,7 @@ public interface Errors { DiagnosticFactory2 DEPRECATED_IDENTITY_EQUALS = DiagnosticFactory2.create(WARNING); DiagnosticFactory2 IMPLICIT_BOXING_IN_IDENTITY_EQUALS = DiagnosticFactory2.create(WARNING); + DiagnosticFactory2 FORBIDDEN_IDENTITY_EQUALS = DiagnosticFactory2.create(ERROR); 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 48a407d34b8..ecb29dc30bd 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java @@ -393,6 +393,7 @@ public class DefaultErrorMessages { 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(FORBIDDEN_IDENTITY_EQUALS, "Identity equality for arguments of types {0} and {1} is forbidden", 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 ab492ed5a6b..9a05b291aff 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/BasicExpressionTypingVisitor.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/BasicExpressionTypingVisitor.java @@ -39,7 +39,6 @@ import org.jetbrains.kotlin.lexer.KtKeywordToken; import org.jetbrains.kotlin.lexer.KtTokens; import org.jetbrains.kotlin.name.Name; import org.jetbrains.kotlin.psi.*; -import org.jetbrains.kotlin.psi.psiUtil.KtPsiUtilKt; import org.jetbrains.kotlin.psi.psiUtil.PsiUtilsKt; import org.jetbrains.kotlin.psi.psiUtil.ReservedCheckingKt; import org.jetbrains.kotlin.resolve.*; @@ -1069,7 +1068,7 @@ public class BasicExpressionTypingVisitor extends ExpressionTypingVisitor { ensureNonemptyIntersectionOfOperandTypes(expression, context); // TODO : Check comparison pointlessness result = TypeInfoFactoryKt.createTypeInfo(components.builtIns.getBooleanType(), context); - checkIdentityOnPrimitiveTypes(expression, context); + checkIdentityOnPrimitiveOrInlineClassTypes(expression, context); } else if (OperatorConventions.IN_OPERATIONS.contains(operationType)) { ValueArgument leftArgument = CallMaker.makeValueArgument(left, left != null ? left : operationSign); @@ -1091,15 +1090,23 @@ public class BasicExpressionTypingVisitor extends ExpressionTypingVisitor { return components.dataFlowAnalyzer.checkType(result, expression, contextWithExpectedType); } - private static void checkIdentityOnPrimitiveTypes(@NotNull KtBinaryExpression expression, @NotNull ExpressionTypingContext context) { + private static void checkIdentityOnPrimitiveOrInlineClassTypes( + @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)); + if (KotlinTypeChecker.DEFAULT.equalTypes(leftType, rightType)) { + if (KotlinBuiltIns.isPrimitiveType(leftType)) { + context.trace.report(DEPRECATED_IDENTITY_EQUALS.on(expression, leftType, rightType)); + } + else if (InlineClassesUtilsKt.isInlineClassType(leftType)) { + context.trace.report(FORBIDDEN_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)); diff --git a/compiler/testData/diagnostics/tests/inlineClasses/identityComparisonWithInlineClasses.kt b/compiler/testData/diagnostics/tests/inlineClasses/identityComparisonWithInlineClasses.kt new file mode 100644 index 00000000000..9f7ed6e3eb9 --- /dev/null +++ b/compiler/testData/diagnostics/tests/inlineClasses/identityComparisonWithInlineClasses.kt @@ -0,0 +1,15 @@ +// !LANGUAGE: +InlineClasses +// !DIAGNOSTICS: -UNUSED_VARIABLE + +inline class Foo(val x: Int) +inline class Bar(val y: String) + +fun test(f1: Foo, f2: Foo, b1: Bar, fn1: Foo?, fn2: Foo?) { + val a1 = f1 === f2 || f1 !== f2 + val a2 = f1 === f1 + val a3 = f1 === b1 || f1 !== b1 + + val c1 = fn1 === fn2 || fn1 !== fn2 + val c2 = f1 === fn1 || f1 !== fn1 + val c3 = b1 === fn1 || b1 !== fn1 +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/inlineClasses/identityComparisonWithInlineClasses.txt b/compiler/testData/diagnostics/tests/inlineClasses/identityComparisonWithInlineClasses.txt new file mode 100644 index 00000000000..a76d2640edb --- /dev/null +++ b/compiler/testData/diagnostics/tests/inlineClasses/identityComparisonWithInlineClasses.txt @@ -0,0 +1,19 @@ +package + +public fun test(/*0*/ f1: Foo, /*1*/ f2: Foo, /*2*/ b1: Bar, /*3*/ fn1: Foo?, /*4*/ fn2: Foo?): kotlin.Unit + +public final inline class Bar { + public constructor Bar(/*0*/ y: kotlin.String) + public final val y: kotlin.String + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public final inline class Foo { + public constructor Foo(/*0*/ x: kotlin.Int) + public final val x: kotlin.Int + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} diff --git a/compiler/testData/diagnostics/testsWithUnsignedTypes/forbiddenEqualsOnUnsignedTypes.kt b/compiler/testData/diagnostics/testsWithUnsignedTypes/forbiddenEqualsOnUnsignedTypes.kt new file mode 100644 index 00000000000..d4d1f7060a2 --- /dev/null +++ b/compiler/testData/diagnostics/testsWithUnsignedTypes/forbiddenEqualsOnUnsignedTypes.kt @@ -0,0 +1,25 @@ +// !LANGUAGE: +InlineClasses +// !SKIP_METADATA_VERSION_CHECK +// !DIAGNOSTICS: -UNUSED_VARIABLE + +fun test( + ub1: UByte, ub2: UByte, + us1: UShort, us2: UShort, + ui1: UInt, ui2: UInt, + ul1: ULong, ul2: ULong +) { + val ub = ub1 === ub2 || ub1 !== ub2 + val us = us1 === us2 || us1 !== us2 + val ui = ui1 === ui2 || ui1 !== ui2 + val ul = ul1 === ul2 || ul1 !== ul2 + + val u = ub1 === ul1 + + val a1 = 1u === 2u || 1u !== 2u + val a2 = 0xFFFF_FFFF_FFFF_FFFFu === 0xFFFF_FFFF_FFFF_FFFFu + + val bu1 = 1u + val bu2 = 1u + + val c1 = bu1 === bu2 || bu1 !== bu2 +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/testsWithUnsignedTypes/forbiddenEqualsOnUnsignedTypes.txt b/compiler/testData/diagnostics/testsWithUnsignedTypes/forbiddenEqualsOnUnsignedTypes.txt new file mode 100644 index 00000000000..8803d0f7451 --- /dev/null +++ b/compiler/testData/diagnostics/testsWithUnsignedTypes/forbiddenEqualsOnUnsignedTypes.txt @@ -0,0 +1,3 @@ +package + +public fun test(/*0*/ ub1: kotlin.UByte, /*1*/ ub2: kotlin.UByte, /*2*/ us1: kotlin.UShort, /*3*/ us2: kotlin.UShort, /*4*/ ui1: kotlin.UInt, /*5*/ ui2: kotlin.UInt, /*6*/ ul1: kotlin.ULong, /*7*/ ul2: kotlin.ULong): kotlin.Unit diff --git a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java index c389787247c..a405d87f224 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java @@ -10706,6 +10706,11 @@ public class DiagnosticsTestGenerated extends AbstractDiagnosticsTest { runTest("compiler/testData/diagnostics/tests/inlineClasses/basicInlineClassDeclarationDisabled.kt"); } + @TestMetadata("identityComparisonWithInlineClasses.kt") + public void testIdentityComparisonWithInlineClasses() throws Exception { + runTest("compiler/testData/diagnostics/tests/inlineClasses/identityComparisonWithInlineClasses.kt"); + } + @TestMetadata("inlineClassDeclarationCheck.kt") public void testInlineClassDeclarationCheck() throws Exception { runTest("compiler/testData/diagnostics/tests/inlineClasses/inlineClassDeclarationCheck.kt"); diff --git a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsWithUnsignedTypesGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsWithUnsignedTypesGenerated.java index 2b67c159938..9421d8893a6 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsWithUnsignedTypesGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsWithUnsignedTypesGenerated.java @@ -29,6 +29,11 @@ public class DiagnosticsWithUnsignedTypesGenerated extends AbstractDiagnosticsWi KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/diagnostics/testsWithUnsignedTypes"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.ANY, true); } + @TestMetadata("forbiddenEqualsOnUnsignedTypes.kt") + public void testForbiddenEqualsOnUnsignedTypes() throws Exception { + runTest("compiler/testData/diagnostics/testsWithUnsignedTypes/forbiddenEqualsOnUnsignedTypes.kt"); + } + @TestMetadata("overloadResolutionOfBasicOperations.kt") public void testOverloadResolutionOfBasicOperations() throws Exception { runTest("compiler/testData/diagnostics/testsWithUnsignedTypes/overloadResolutionOfBasicOperations.kt"); diff --git a/compiler/tests/org/jetbrains/kotlin/checkers/javac/DiagnosticsUsingJavacTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/javac/DiagnosticsUsingJavacTestGenerated.java index 171d092d4be..6d85c17e745 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/javac/DiagnosticsUsingJavacTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/javac/DiagnosticsUsingJavacTestGenerated.java @@ -10706,6 +10706,11 @@ public class DiagnosticsUsingJavacTestGenerated extends AbstractDiagnosticsUsing runTest("compiler/testData/diagnostics/tests/inlineClasses/basicInlineClassDeclarationDisabled.kt"); } + @TestMetadata("identityComparisonWithInlineClasses.kt") + public void testIdentityComparisonWithInlineClasses() throws Exception { + runTest("compiler/testData/diagnostics/tests/inlineClasses/identityComparisonWithInlineClasses.kt"); + } + @TestMetadata("inlineClassDeclarationCheck.kt") public void testInlineClassDeclarationCheck() throws Exception { runTest("compiler/testData/diagnostics/tests/inlineClasses/inlineClassDeclarationCheck.kt");