diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/intrinsics/Equals.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/intrinsics/Equals.kt index dae2903af2c..381485cd41f 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/intrinsics/Equals.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/intrinsics/Equals.kt @@ -20,6 +20,10 @@ import org.jetbrains.kotlin.codegen.AsmUtil import org.jetbrains.kotlin.codegen.Callable import org.jetbrains.kotlin.codegen.CallableMethod import org.jetbrains.kotlin.resolve.jvm.AsmTypes.OBJECT_TYPE +import org.jetbrains.org.objectweb.asm.Type + +internal val equalsMethodDescriptor: String = + Type.getMethodDescriptor(Type.BOOLEAN_TYPE, Type.getType(Any::class.java)); class Equals : IntrinsicMethod() { override fun toCallable(method: CallableMethod): Callable = @@ -32,3 +36,15 @@ class Equals : IntrinsicMethod() { AsmUtil.genAreEqualCall(it) } } + +class ConsistentEquals(private val lhsType: Type) : IntrinsicMethod() { + override fun toCallable(method: CallableMethod): Callable = + createBinaryIntrinsicCallable( + method.returnType, + OBJECT_TYPE, + nullOrObject(method.dispatchReceiverType), + nullOrObject(method.extensionReceiverType) + ) { + it.invokevirtual(lhsType.internalName, "equals", equalsMethodDescriptor, false) + } +} diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/intrinsics/IntrinsicMethods.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/intrinsics/IntrinsicMethods.java index 9d79726e5a4..f08e607f1a4 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/intrinsics/IntrinsicMethods.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/intrinsics/IntrinsicMethods.java @@ -22,6 +22,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.kotlin.builtins.KotlinBuiltIns; import org.jetbrains.kotlin.builtins.PrimitiveType; +import org.jetbrains.kotlin.codegen.AsmUtil; import org.jetbrains.kotlin.config.JvmTarget; import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor; import org.jetbrains.kotlin.name.FqName; @@ -29,6 +30,7 @@ import org.jetbrains.kotlin.name.FqNameUnsafe; import org.jetbrains.kotlin.name.Name; import org.jetbrains.kotlin.resolve.jvm.JvmPrimitiveType; import org.jetbrains.kotlin.types.expressions.OperatorConventions; +import org.jetbrains.org.objectweb.asm.Type; import static org.jetbrains.kotlin.builtins.KotlinBuiltIns.*; import static org.jetbrains.org.objectweb.asm.Opcodes.*; @@ -46,7 +48,6 @@ public class IntrinsicMethods { private static final IntrinsicMethod RANGE_TO = new RangeTo(); private static final IntrinsicMethod INC = new Increment(1); private static final IntrinsicMethod DEC = new Increment(-1); - private final IntrinsicMethod HASH_CODE; private static final IntrinsicMethod ARRAY_SIZE = new ArraySize(); private static final Equals EQUALS = new Equals(); @@ -61,7 +62,10 @@ public class IntrinsicMethods { private final IntrinsicsMap intrinsicsMap = new IntrinsicsMap(); public IntrinsicMethods(JvmTarget jvmTarget) { - HASH_CODE = new HashCode(jvmTarget); + this(jvmTarget, true); + } + + public IntrinsicMethods(JvmTarget jvmTarget, boolean useConsistentEqualsForPrimitiveWrappers) { intrinsicsMap.registerIntrinsic(KOTLIN_JVM, RECEIVER_PARAMETER_FQ_NAME, "javaClass", -1, JavaClassProperty.INSTANCE); intrinsicsMap.registerIntrinsic(KOTLIN_JVM, KotlinBuiltIns.FQ_NAMES.kClass, "java", -1, new KClassJavaProperty()); intrinsicsMap.registerIntrinsic(KotlinBuiltIns.FQ_NAMES.kCallable.toSafe(), null, "name", -1, new KCallableNameProperty()); @@ -94,10 +98,20 @@ public class IntrinsicMethods { declareIntrinsicFunction(typeFqName, "dec", 0, DEC); } + IntrinsicMethod hashCode = new HashCode(jvmTarget); for (PrimitiveType type : PrimitiveType.values()) { FqName typeFqName = type.getTypeFqName(); - declareIntrinsicFunction(typeFqName, "equals", 1, EQUALS); - declareIntrinsicFunction(typeFqName, "hashCode", 0, HASH_CODE); + IntrinsicMethod equalsMethod; + if (useConsistentEqualsForPrimitiveWrappers) { + Type wrapperType = AsmUtil.asmTypeByFqNameWithoutInnerClasses(JvmPrimitiveType.get(type).getWrapperFqName()); + equalsMethod = new ConsistentEquals(wrapperType); + } + else { + equalsMethod = EQUALS; + } + + declareIntrinsicFunction(typeFqName, "equals", 1, equalsMethod); + declareIntrinsicFunction(typeFqName, "hashCode", 0, hashCode); declareIntrinsicFunction(typeFqName, "toString", 0, TO_STRING); intrinsicsMap.registerIntrinsic( diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/state/GenerationState.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/state/GenerationState.kt index b792f3326b0..1dfa1423bf3 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/state/GenerationState.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/state/GenerationState.kt @@ -126,6 +126,8 @@ class GenerationState @JvmOverloads constructor( extraJvmDiagnosticsTrace.bindingContext.diagnostics } + val languageVersionSettings = configuration.languageVersionSettings + val target = configuration.get(JVMConfigurationKeys.JVM_TARGET) ?: JvmTarget.DEFAULT val isJvm8Target: Boolean = target == JvmTarget.JVM_1_8 val isJvm8TargetWithDefaults: Boolean = isJvm8Target && configuration.getBoolean(JVMConfigurationKeys.JVM8_TARGET_WITH_DEFAULTS) @@ -140,7 +142,7 @@ class GenerationState @JvmOverloads constructor( this.bindingContext, classBuilderMode, IncompatibleClassTrackerImpl(extraJvmDiagnosticsTrace), this.moduleName, isJvm8Target, isJvm8TargetWithDefaults ) - val intrinsics: IntrinsicMethods = IntrinsicMethods(target) + val intrinsics: IntrinsicMethods = IntrinsicMethods(target, languageVersionSettings.supportsFeature(LanguageFeature.ConsistentExplicitEqualsForPrimitiveWrappers)) val samWrapperClasses: SamWrapperClasses = SamWrapperClasses(this) val inlineCycleReporter: InlineCycleReporter = InlineCycleReporter(diagnostics) val mappingsClassesForWhenByEnum: MappingsClassesForWhenByEnum = MappingsClassesForWhenByEnum(this) @@ -158,8 +160,6 @@ class GenerationState @JvmOverloads constructor( var hasResult: Boolean = false } - val languageVersionSettings = configuration.languageVersionSettings - val isCallAssertionsDisabled: Boolean = configuration.getBoolean(JVMConfigurationKeys.DISABLE_CALL_ASSERTIONS) val isReceiverAssertionsDisabled: Boolean = configuration.getBoolean(JVMConfigurationKeys.DISABLE_RECEIVER_ASSERTIONS) || diff --git a/compiler/testData/codegen/box/platformTypes/primitives/equalsNull_lv11.kt b/compiler/testData/codegen/box/platformTypes/primitives/equalsNull_lv11.kt new file mode 100644 index 00000000000..981310ecabe --- /dev/null +++ b/compiler/testData/codegen/box/platformTypes/primitives/equalsNull_lv11.kt @@ -0,0 +1,39 @@ +// LANGUAGE_VERSION: 1.1 +// TARGET_BACKEND: JVM +// WITH_RUNTIME +// FILE: test.kt +import kotlin.test.* + +fun box(): String { + assertEquals(J.BOOL_NULL.equals(null), true) + assertEquals(J.CHAR_NULL.equals(null), true) + assertEquals(J.BYTE_NULL.equals(null), true) + assertEquals(J.SHORT_NULL.equals(null), true) + assertEquals(J.INT_NULL.equals(null), true) + assertEquals(J.LONG_NULL.equals(null), true) + assertEquals(J.FLOAT_NULL.equals(null), true) + assertEquals(J.DOUBLE_NULL.equals(null), true) + + assertEquals(J.BOOL_NULL == null, true) + assertEquals(J.CHAR_NULL == null, true) + assertEquals(J.BYTE_NULL == null, true) + assertEquals(J.SHORT_NULL == null, true) + assertEquals(J.INT_NULL == null, true) + assertEquals(J.LONG_NULL == null, true) + assertEquals(J.FLOAT_NULL == null, true) + assertEquals(J.DOUBLE_NULL == null, true) + + return "OK" +} + +// FILE: J.java +public class J { + public static Boolean BOOL_NULL = null; + public static Character CHAR_NULL = null; + public static Byte BYTE_NULL = null; + public static Short SHORT_NULL = null; + public static Integer INT_NULL = null; + public static Long LONG_NULL = null; + public static Float FLOAT_NULL = null; + public static Double DOUBLE_NULL = null; +} \ No newline at end of file diff --git a/compiler/testData/codegen/box/platformTypes/primitives/equalsNull_lv12.kt b/compiler/testData/codegen/box/platformTypes/primitives/equalsNull_lv12.kt new file mode 100644 index 00000000000..1a4f0c850c4 --- /dev/null +++ b/compiler/testData/codegen/box/platformTypes/primitives/equalsNull_lv12.kt @@ -0,0 +1,39 @@ +// LANGUAGE_VERSION: 1.2 +// TARGET_BACKEND: JVM +// WITH_RUNTIME +// FILE: test.kt +import kotlin.test.* + +fun box(): String { + assertFailsWith { J.BOOL_NULL.equals(null) } + assertFailsWith { J.CHAR_NULL.equals(null) } + assertFailsWith { J.BYTE_NULL.equals(null) } + assertFailsWith { J.SHORT_NULL.equals(null) } + assertFailsWith { J.INT_NULL.equals(null) } + assertFailsWith { J.LONG_NULL.equals(null) } + assertFailsWith { J.FLOAT_NULL.equals(null) } + assertFailsWith { J.DOUBLE_NULL.equals(null) } + + assertEquals(J.BOOL_NULL == null, true) + assertEquals(J.CHAR_NULL == null, true) + assertEquals(J.BYTE_NULL == null, true) + assertEquals(J.SHORT_NULL == null, true) + assertEquals(J.INT_NULL == null, true) + assertEquals(J.LONG_NULL == null, true) + assertEquals(J.FLOAT_NULL == null, true) + assertEquals(J.DOUBLE_NULL == null, true) + + return "OK" +} + +// FILE: J.java +public class J { + public static Boolean BOOL_NULL = null; + public static Character CHAR_NULL = null; + public static Byte BYTE_NULL = null; + public static Short SHORT_NULL = null; + public static Integer INT_NULL = null; + public static Long LONG_NULL = null; + public static Float FLOAT_NULL = null; + public static Double DOUBLE_NULL = null; +} \ No newline at end of file diff --git a/compiler/tests-ir-jvm/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java b/compiler/tests-ir-jvm/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java index a0916dfc998..643bbff8206 100644 --- a/compiler/tests-ir-jvm/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java +++ b/compiler/tests-ir-jvm/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java @@ -12388,6 +12388,18 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes doTest(fileName); } + @TestMetadata("equalsNull_lv11.kt") + public void testEqualsNull_lv11() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/platformTypes/primitives/equalsNull_lv11.kt"); + doTest(fileName); + } + + @TestMetadata("equalsNull_lv12.kt") + public void testEqualsNull_lv12() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/platformTypes/primitives/equalsNull_lv12.kt"); + doTest(fileName); + } + @TestMetadata("hashCode.kt") public void testHashCode() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/platformTypes/primitives/hashCode.kt"); diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java index 74b9e236cc0..3cad21a9b14 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java @@ -12388,6 +12388,18 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest { doTest(fileName); } + @TestMetadata("equalsNull_lv11.kt") + public void testEqualsNull_lv11() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/platformTypes/primitives/equalsNull_lv11.kt"); + doTest(fileName); + } + + @TestMetadata("equalsNull_lv12.kt") + public void testEqualsNull_lv12() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/platformTypes/primitives/equalsNull_lv12.kt"); + doTest(fileName); + } + @TestMetadata("hashCode.kt") public void testHashCode() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/platformTypes/primitives/hashCode.kt"); diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java index 4c6f6d88d16..91cb6377519 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java @@ -12388,6 +12388,18 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes doTest(fileName); } + @TestMetadata("equalsNull_lv11.kt") + public void testEqualsNull_lv11() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/platformTypes/primitives/equalsNull_lv11.kt"); + doTest(fileName); + } + + @TestMetadata("equalsNull_lv12.kt") + public void testEqualsNull_lv12() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/platformTypes/primitives/equalsNull_lv12.kt"); + doTest(fileName); + } + @TestMetadata("hashCode.kt") public void testHashCode() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/platformTypes/primitives/hashCode.kt"); diff --git a/compiler/util/src/org/jetbrains/kotlin/config/LanguageVersionSettings.kt b/compiler/util/src/org/jetbrains/kotlin/config/LanguageVersionSettings.kt index 419b0e18b5c..8b7a48b0da1 100644 --- a/compiler/util/src/org/jetbrains/kotlin/config/LanguageVersionSettings.kt +++ b/compiler/util/src/org/jetbrains/kotlin/config/LanguageVersionSettings.kt @@ -61,6 +61,7 @@ enum class LanguageFeature( LateinitLocalVariables(KOTLIN_1_2), InnerClassInEnumEntryClass(KOTLIN_1_2), CallableReferencesToClassMembersWithEmptyLHS(KOTLIN_1_2), + ConsistentExplicitEqualsForPrimitiveWrappers(KOTLIN_1_2), JvmPackageName(KOTLIN_1_2), RestrictionOfValReassignmentViaBackingField(KOTLIN_1_3),