From f40200690513b3291f8645e91936507c0c3f8f36 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Thu, 10 Aug 2023 17:29:25 +0200 Subject: [PATCH 1/7] Add custom implementations for toString, equals and hashCode --- CHANGELOG.md | 2 + .../kotlin/internal/RealmObjectHelper.kt | 21 +++ .../RealmModelDefaultMethodGeneration.kt | 115 +++++++++++++++ .../compiler/RealmModelLoweringExtension.kt | 4 + .../RealmModelSyntheticCompanionExtension.kt | 50 ------- .../RealmModelSyntheticMethodsExtension.kt | 137 ++++++++++++++++++ .../io/realm/kotlin/compiler/Registrar.kt | 7 + .../01_AFTER.ValidateIrBeforeLowering.ir | 100 +++++++++---- .../01_AFTER.ValidateIrBeforeLowering.ir | 27 +++- .../kotlin/test/common/RealmObjectTests.kt | 75 ++++++++++ 10 files changed, 454 insertions(+), 84 deletions(-) create mode 100644 packages/plugin-compiler/src/main/kotlin/io/realm/kotlin/compiler/RealmModelDefaultMethodGeneration.kt create mode 100644 packages/plugin-compiler/src/main/kotlin/io/realm/kotlin/compiler/RealmModelSyntheticMethodsExtension.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b5d3adfab..a84b3739f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ * None. ### Enhancements +* Realm model classes now generate custom `toString`, `equals` and `hashCode` implementations. This makes it possible to compare by object reference across multiple collections. Note that two objects at different versions will not be considered equal, even +if the content is the same. Custom implementations of these methods will be respected if they are present. (Issue [#1097](https://github.com/realm/realm-kotlin/issues/1097)) * Support for performing geospatial queries using the new classes: `GeoPoint`, `GeoCircle`, `GeoBox`, and `GeoPolygon`. See `GeoPoint` documentation on how to persist locations. (Issue [#1403](https://github.com/realm/realm-kotlin/pull/1403)) * [Sync] Add support for customizing authorization headers and adding additional custom headers to all Atlas App service requests with `AppConfiguration.Builder.authorizationHeaderName()` and `AppConfiguration.Builder.addCustomRequestHeader(...)`. (Issue [#1453](https://github.com/realm/realm-kotlin/pull/1453)) diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmObjectHelper.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmObjectHelper.kt index c34965306b..eca6902752 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmObjectHelper.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmObjectHelper.kt @@ -1145,6 +1145,27 @@ internal object RealmObjectHelper { } } + @OptIn(ExperimentalStdlibApi::class) + @Suppress("unused") // Called from generated code + internal fun realmToString(obj: BaseRealmObject): String { +// if (objReference == null) { +// return "${obj::class.qualifiedName}[Unmanaged]@${hashCode().toHexString()}" +// } else { + +// } + TODO("Custom toString") + } + + @Suppress("unused") // Called from generated code + internal inline fun realmEquals(obj: BaseRealmObject, other: Any?): Boolean { + TODO("BOOM") + } + + @Suppress("unused") // Called from generated code + internal inline fun realmHashCode(obj: BaseRealmObject): Int { + return 42 + } + private fun checkPropertyType( obj: RealmObjectReference, propertyName: String, diff --git a/packages/plugin-compiler/src/main/kotlin/io/realm/kotlin/compiler/RealmModelDefaultMethodGeneration.kt b/packages/plugin-compiler/src/main/kotlin/io/realm/kotlin/compiler/RealmModelDefaultMethodGeneration.kt new file mode 100644 index 0000000000..97fad72e3b --- /dev/null +++ b/packages/plugin-compiler/src/main/kotlin/io/realm/kotlin/compiler/RealmModelDefaultMethodGeneration.kt @@ -0,0 +1,115 @@ +package io.realm.kotlin.compiler + +import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext +import org.jetbrains.kotlin.ir.builders.irGet +import org.jetbrains.kotlin.ir.builders.irGetObject +import org.jetbrains.kotlin.ir.builders.irReturn +import org.jetbrains.kotlin.ir.declarations.IrClass +import org.jetbrains.kotlin.ir.declarations.IrProperty +import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction +import org.jetbrains.kotlin.ir.expressions.impl.IrCallImpl +import org.jetbrains.kotlin.ir.types.IrType +import org.jetbrains.kotlin.ir.util.functions +import org.jetbrains.kotlin.name.Name + +/** + * Class responsible for adding Realm specific logic to the default object methods like: + * - toString() + * - hashCode() + * - equals() + */ +class RealmModelDefaultMethodGeneration(private val pluginContext: IrPluginContext) { + + private val realmObjectHelper: IrClass = pluginContext.lookupClassOrThrow(FqNames.REALM_OBJECT_HELPER) + private val realmToString: IrSimpleFunction = realmObjectHelper.lookupFunction(Name.identifier("realmToString")) + private val realmEquals: IrSimpleFunction = realmObjectHelper.lookupFunction(Name.identifier("realmEquals")) + private val realmHashCode: IrSimpleFunction = realmObjectHelper.lookupFunction(Name.identifier("realmHashCode")) + private lateinit var objectReferenceProperty: IrProperty + private lateinit var objectReferenceType: IrType + + fun addDefaultMethods(irClass: IrClass) { + objectReferenceProperty = irClass.lookupProperty(Names.OBJECT_REFERENCE) + objectReferenceType = objectReferenceProperty.backingField!!.type + + if (syntheticMethodExists(irClass, "toString")) { + addToStringMethodBody(irClass) + } + if (syntheticMethodExists(irClass, "hashCode")) { + addHashCodeMethod(irClass) + } + if (syntheticMethodExists(irClass, "equals")) { + addEqualsMethod(irClass) + } + } + + /** + * Checks if a synthetic method exists in the given class. Methods in super classes + * are ignored, only methods actually declared in the class will return `true`. + * + * These methods are created by an earlier step by the Realm compiler plugin and are + * recognized by not being fake and having an empty body.ß + */ + private fun syntheticMethodExists(irClass: IrClass, methodName: String): Boolean { + return irClass.functions.firstOrNull { + !it.isFakeOverride && it.body == null && it.name == Name.identifier(methodName) + } != null + } + + private fun addEqualsMethod(irClass: IrClass) { + val function: IrSimpleFunction = irClass.symbol.owner.functions.single { it.name.toString() == "equals" } + function.body = pluginContext.blockBody(function.symbol) { + +irReturn( + IrCallImpl( + startOffset = startOffset, + endOffset = endOffset, + type = pluginContext.irBuiltIns.booleanType, + symbol = realmEquals.symbol, + typeArgumentsCount = 0, + valueArgumentsCount = 2 + ).apply { + dispatchReceiver = irGetObject(realmObjectHelper.symbol) + putValueArgument(0, irGet(function.dispatchReceiverParameter!!.type, function.dispatchReceiverParameter!!.symbol)) + putValueArgument(1, irGet(function.dispatchReceiverParameter!!.type, function.dispatchReceiverParameter!!.symbol)) + } + ) + } + } + + private fun addHashCodeMethod(irClass: IrClass) { + val function: IrSimpleFunction = irClass.symbol.owner.functions.single { it.name.toString() == "hashCode" } + function.body = pluginContext.blockBody(function.symbol) { + +irReturn( + IrCallImpl( + startOffset = startOffset, + endOffset = endOffset, + type = pluginContext.irBuiltIns.intType, + symbol = realmHashCode.symbol, + typeArgumentsCount = 0, + valueArgumentsCount = 1 + ).apply { + dispatchReceiver = irGetObject(realmObjectHelper.symbol) + putValueArgument(0, irGet(function.dispatchReceiverParameter!!.type, function.dispatchReceiverParameter!!.symbol)) + } + ) + } + } + + private fun addToStringMethodBody(irClass: IrClass) { + val function: IrSimpleFunction = irClass.symbol.owner.functions.single { it.name.toString() == "toString" } + function.body = pluginContext.blockBody(function.symbol) { + +irReturn( + IrCallImpl( + startOffset = startOffset, + endOffset = endOffset, + type = pluginContext.irBuiltIns.stringType, + symbol = realmToString.symbol, + typeArgumentsCount = 0, + valueArgumentsCount = 1 + ).apply { + dispatchReceiver = irGetObject(realmObjectHelper.symbol) + putValueArgument(0, irGet(function.dispatchReceiverParameter!!.type, function.dispatchReceiverParameter!!.symbol)) + } + ) + } + } +} \ No newline at end of file diff --git a/packages/plugin-compiler/src/main/kotlin/io/realm/kotlin/compiler/RealmModelLoweringExtension.kt b/packages/plugin-compiler/src/main/kotlin/io/realm/kotlin/compiler/RealmModelLoweringExtension.kt index 9529a5124b..f45f3c6289 100644 --- a/packages/plugin-compiler/src/main/kotlin/io/realm/kotlin/compiler/RealmModelLoweringExtension.kt +++ b/packages/plugin-compiler/src/main/kotlin/io/realm/kotlin/compiler/RealmModelLoweringExtension.kt @@ -107,6 +107,10 @@ private class RealmModelLowering(private val pluginContext: IrPluginContext) : C // Modify properties accessor to generate custom getter/setter AccessorModifierIrGeneration(pluginContext).modifyPropertiesAndCollectSchema(irClass) + // Add custom toString, equals and hashCode methods + val methodGenerator = RealmModelDefaultMethodGeneration(pluginContext) + methodGenerator.addDefaultMethods(irClass) + // Add body for synthetic companion methods val companion = irClass.companionObject() ?: fatalError("RealmObject without companion: ${irClass.kotlinFqName}") generator.addCompanionFields(irClass, companion, SchemaCollector.properties[irClass]) diff --git a/packages/plugin-compiler/src/main/kotlin/io/realm/kotlin/compiler/RealmModelSyntheticCompanionExtension.kt b/packages/plugin-compiler/src/main/kotlin/io/realm/kotlin/compiler/RealmModelSyntheticCompanionExtension.kt index 02b8658fb5..f72760985b 100644 --- a/packages/plugin-compiler/src/main/kotlin/io/realm/kotlin/compiler/RealmModelSyntheticCompanionExtension.kt +++ b/packages/plugin-compiler/src/main/kotlin/io/realm/kotlin/compiler/RealmModelSyntheticCompanionExtension.kt @@ -19,12 +19,9 @@ package io.realm.kotlin.compiler import io.realm.kotlin.compiler.Names.REALM_OBJECT_COMPANION_NEW_INSTANCE_METHOD import io.realm.kotlin.compiler.Names.REALM_OBJECT_COMPANION_SCHEMA_METHOD import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor -import org.jetbrains.kotlin.descriptors.ClassConstructorDescriptor import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.descriptors.DescriptorVisibilities import org.jetbrains.kotlin.descriptors.Modality -import org.jetbrains.kotlin.descriptors.PackageFragmentDescriptor -import org.jetbrains.kotlin.descriptors.PropertyDescriptor import org.jetbrains.kotlin.descriptors.SimpleFunctionDescriptor import org.jetbrains.kotlin.descriptors.annotations.Annotations import org.jetbrains.kotlin.descriptors.impl.SimpleFunctionDescriptorImpl @@ -33,11 +30,6 @@ import org.jetbrains.kotlin.name.SpecialNames.DEFAULT_NAME_FOR_COMPANION_OBJECT import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.descriptorUtil.builtIns import org.jetbrains.kotlin.resolve.extensions.SyntheticResolveExtension -import org.jetbrains.kotlin.resolve.lazy.LazyClassContext -import org.jetbrains.kotlin.resolve.lazy.declarations.ClassMemberDeclarationProvider -import org.jetbrains.kotlin.resolve.lazy.declarations.PackageMemberDeclarationProvider -import org.jetbrains.kotlin.types.KotlinType -import java.util.ArrayList /** * Triggers generation of companion objects and ensures that the companion object implement the @@ -58,48 +50,6 @@ class RealmModelSyntheticCompanionExtension : SyntheticResolveExtension { } } - override fun addSyntheticSupertypes( - thisDescriptor: ClassDescriptor, - supertypes: MutableList - ) { - } - - override fun generateSyntheticClasses( - thisDescriptor: ClassDescriptor, - name: Name, - ctx: LazyClassContext, - declarationProvider: ClassMemberDeclarationProvider, - result: MutableSet - ) { - } - - override fun generateSyntheticClasses( - thisDescriptor: PackageFragmentDescriptor, - name: Name, - ctx: LazyClassContext, - declarationProvider: PackageMemberDeclarationProvider, - result: MutableSet - ) { - } - - override fun generateSyntheticProperties( - thisDescriptor: ClassDescriptor, - name: Name, - bindingContext: BindingContext, - fromSupertypes: ArrayList, - result: MutableSet - ) { - } - - override fun generateSyntheticSecondaryConstructors( - thisDescriptor: ClassDescriptor, - bindingContext: BindingContext, - result: MutableCollection - ) { - } - - override fun getSyntheticNestedClassNames(thisDescriptor: ClassDescriptor): List = emptyList() - override fun getSyntheticFunctionNames(thisDescriptor: ClassDescriptor): List { return when { thisDescriptor.isRealmObjectCompanion -> { diff --git a/packages/plugin-compiler/src/main/kotlin/io/realm/kotlin/compiler/RealmModelSyntheticMethodsExtension.kt b/packages/plugin-compiler/src/main/kotlin/io/realm/kotlin/compiler/RealmModelSyntheticMethodsExtension.kt new file mode 100644 index 0000000000..27318e393a --- /dev/null +++ b/packages/plugin-compiler/src/main/kotlin/io/realm/kotlin/compiler/RealmModelSyntheticMethodsExtension.kt @@ -0,0 +1,137 @@ +/* + * Copyright 2020 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.realm.kotlin.compiler + +import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor +import org.jetbrains.kotlin.descriptors.ClassDescriptor +import org.jetbrains.kotlin.descriptors.DescriptorVisibilities +import org.jetbrains.kotlin.descriptors.Modality +import org.jetbrains.kotlin.descriptors.SimpleFunctionDescriptor +import org.jetbrains.kotlin.descriptors.annotations.Annotations +import org.jetbrains.kotlin.descriptors.impl.SimpleFunctionDescriptorImpl +import org.jetbrains.kotlin.descriptors.impl.ValueParameterDescriptorImpl +import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.resolve.BindingContext +import org.jetbrains.kotlin.resolve.descriptorUtil.builtIns +import org.jetbrains.kotlin.resolve.descriptorUtil.parents +import org.jetbrains.kotlin.resolve.extensions.SyntheticResolveExtension +import org.jetbrains.kotlin.types.SimpleType + +/** + * Triggers generation of synthetic methods on Realm model classes, in particular + * `toString()`, `equals()` and `hashCode()`. + */ +class RealmModelSyntheticMethodsExtension : SyntheticResolveExtension { + + override fun generateSyntheticMethods( + thisDescriptor: ClassDescriptor, + name: Name, + bindingContext: BindingContext, + fromSupertypes: List, + result: MutableCollection + ) { + if (thisDescriptor.isRealmObject + && !thisDescriptor.isCompanionObject /* Do not override companion object methods */ + && !thisDescriptor.isInner /* Do not override inner class methods */ + && !isNestedInRealmModelClass(thisDescriptor) /* do not override nested class methods */ + && result.isEmpty() /* = no method has been declared in the current class */ + ) { + when(name.identifier) { + "toString" -> { + result.add( + createMethod( + classDescriptor = thisDescriptor, + methodName = name, + arguments = emptyList(), + returnType = thisDescriptor.builtIns.stringType + ) + ) + } + "equals" -> { + result.add( + createMethod( + classDescriptor = thisDescriptor, + methodName = name, + arguments = listOf(Pair("other", thisDescriptor.builtIns.nullableAnyType)), + returnType = thisDescriptor.builtIns.booleanType + ) + ) + } + "hashCode" -> { + result.add( + createMethod( + classDescriptor = thisDescriptor, + methodName = name, + arguments = emptyList(), + returnType = thisDescriptor.builtIns.intType + ) + ) + } + } + } + } + + private fun createMethod( + classDescriptor: ClassDescriptor, + methodName: Name, + arguments: List>, + returnType: SimpleType + ): SimpleFunctionDescriptor { + return SimpleFunctionDescriptorImpl.create( + classDescriptor, + Annotations.EMPTY, + methodName, + CallableMemberDescriptor.Kind.SYNTHESIZED, + classDescriptor.source + ).apply { + initialize( + null, + classDescriptor.thisAsReceiverParameter, + emptyList(), + emptyList(), + arguments.map { (argumentName, argumentType) -> + ValueParameterDescriptorImpl( + containingDeclaration = this, + original = null, + index = 0, + annotations = Annotations.EMPTY, + name = Name.identifier(argumentName), + outType = argumentType, + declaresDefaultValue = false, + isCrossinline = false, + isNoinline = false, + varargElementType = null, + source = this.source + ) + }, + returnType, + Modality.OPEN, + DescriptorVisibilities.PUBLIC + ) + } + } + + private fun isNestedInRealmModelClass(classDescriptor: ClassDescriptor): Boolean { + return classDescriptor.parents.firstOrNull { + if (it is ClassDescriptor) { + it.isRealmObject + } else { + false + } + } != null + } +} diff --git a/packages/plugin-compiler/src/main/kotlin/io/realm/kotlin/compiler/Registrar.kt b/packages/plugin-compiler/src/main/kotlin/io/realm/kotlin/compiler/Registrar.kt index e5fadb50a4..ed03b01688 100644 --- a/packages/plugin-compiler/src/main/kotlin/io/realm/kotlin/compiler/Registrar.kt +++ b/packages/plugin-compiler/src/main/kotlin/io/realm/kotlin/compiler/Registrar.kt @@ -69,6 +69,13 @@ class Registrar : ComponentRegistrar { LoadingOrder.LAST, project ) + // Trigger generation of Realm specific methods in model classes: + // toString(), equals() and hashCode() + getExtensionPoint(SyntheticResolveExtension.extensionPointName).registerExtension( + RealmModelSyntheticMethodsExtension(), + LoadingOrder.LAST, + project + ) // Adds RealmObjectInternal properties, rewires accessors and adds static companion // properties and methods getExtensionPoint(IrGenerationExtension.extensionPointName).registerExtension( diff --git a/packages/plugin-compiler/src/test/resources/sample/expected/01_AFTER.ValidateIrBeforeLowering.ir b/packages/plugin-compiler/src/test/resources/sample/expected/01_AFTER.ValidateIrBeforeLowering.ir index 5ef8f93504..f39438d3f0 100644 --- a/packages/plugin-compiler/src/test/resources/sample/expected/01_AFTER.ValidateIrBeforeLowering.ir +++ b/packages/plugin-compiler/src/test/resources/sample/expected/01_AFTER.ValidateIrBeforeLowering.ir @@ -8508,19 +8508,30 @@ MODULE_FRAGMENT name:
RETURN type=kotlin.Nothing from='public final fun (): io.realm.kotlin.schema.RealmClassKind declared in sample.input.Sample.Companion' GET_FIELD 'FIELD PROPERTY_BACKING_FIELD name:io_realm_kotlin_classKind type:io.realm.kotlin.schema.RealmClassKind visibility:private' type=io.realm.kotlin.schema.RealmClassKind origin=null receiver: GET_VAR ': sample.input.Sample.Companion declared in sample.input.Sample.Companion.' type=sample.input.Sample.Companion origin=null - FUN FAKE_OVERRIDE name:equals visibility:public modality:OPEN <> ($this:kotlin.Any, other:kotlin.Any?) returnType:kotlin.Boolean [fake_override,operator] - overridden: - public open fun equals (other: kotlin.Any?): kotlin.Boolean [fake_override,operator] declared in io.realm.kotlin.types.RealmObject - $this: VALUE_PARAMETER name: type:kotlin.Any - VALUE_PARAMETER name:other index:0 type:kotlin.Any? FUN FAKE_OVERRIDE name:hashCode visibility:public modality:OPEN <> ($this:kotlin.Any) returnType:kotlin.Int [fake_override] overridden: public open fun hashCode (): kotlin.Int [fake_override] declared in io.realm.kotlin.types.RealmObject $this: VALUE_PARAMETER name: type:kotlin.Any - FUN FAKE_OVERRIDE name:toString visibility:public modality:OPEN <> ($this:kotlin.Any) returnType:kotlin.String [fake_override] + FUN name:equals visibility:public modality:OPEN <> ($this:sample.input.Sample, other:kotlin.Any?) returnType:kotlin.Int [operator] + overridden: + public open fun equals (other: kotlin.Any?): kotlin.Boolean [fake_override,operator] declared in io.realm.kotlin.types.RealmObject + $this: VALUE_PARAMETER name: type:sample.input.Sample + VALUE_PARAMETER name:other index:0 type:kotlin.Any? + BLOCK_BODY + RETURN type=kotlin.Nothing from='public open fun equals (other: kotlin.Any?): kotlin.Int [operator] declared in sample.input.Sample' + CALL 'internal final fun realmEquals (obj: io.realm.kotlin.types.BaseRealmObject, other: kotlin.Any?): kotlin.Boolean [inline] declared in io.realm.kotlin.internal.RealmObjectHelper' type=kotlin.Boolean origin=null + $this: GET_OBJECT 'CLASS IR_EXTERNAL_DECLARATION_STUB OBJECT name:RealmObjectHelper modality:FINAL visibility:internal superTypes:[kotlin.Any]' type=io.realm.kotlin.internal.RealmObjectHelper + obj: GET_VAR ': sample.input.Sample declared in sample.input.Sample.equals' type=sample.input.Sample origin=null + other: GET_VAR ': sample.input.Sample declared in sample.input.Sample.equals' type=sample.input.Sample origin=null + FUN name:toString visibility:public modality:OPEN <> ($this:sample.input.Sample) returnType:kotlin.String overridden: public open fun toString (): kotlin.String [fake_override] declared in io.realm.kotlin.types.RealmObject - $this: VALUE_PARAMETER name: type:kotlin.Any + $this: VALUE_PARAMETER name: type:sample.input.Sample + BLOCK_BODY + RETURN type=kotlin.Nothing from='public open fun toString (): kotlin.String declared in sample.input.Sample' + CALL 'internal final fun realmToString (obj: io.realm.kotlin.types.BaseRealmObject): kotlin.String declared in io.realm.kotlin.internal.RealmObjectHelper' type=kotlin.String origin=null + $this: GET_OBJECT 'CLASS IR_EXTERNAL_DECLARATION_STUB OBJECT name:RealmObjectHelper modality:FINAL visibility:internal superTypes:[kotlin.Any]' type=io.realm.kotlin.internal.RealmObjectHelper + obj: GET_VAR ': sample.input.Sample declared in sample.input.Sample.toString' type=sample.input.Sample origin=null PROPERTY name:io_realm_kotlin_objectReference visibility:public modality:OPEN [var] FIELD PROPERTY_BACKING_FIELD name:io_realm_kotlin_objectReference type:io.realm.kotlin.internal.RealmObjectReference? visibility:private EXPRESSION_BODY @@ -8795,19 +8806,30 @@ MODULE_FRAGMENT name:
RETURN type=kotlin.Nothing from='public final fun (): io.realm.kotlin.schema.RealmClassKind declared in sample.input.Child.Companion' GET_FIELD 'FIELD PROPERTY_BACKING_FIELD name:io_realm_kotlin_classKind type:io.realm.kotlin.schema.RealmClassKind visibility:private' type=io.realm.kotlin.schema.RealmClassKind origin=null receiver: GET_VAR ': sample.input.Child.Companion declared in sample.input.Child.Companion.' type=sample.input.Child.Companion origin=null - FUN FAKE_OVERRIDE name:equals visibility:public modality:OPEN <> ($this:kotlin.Any, other:kotlin.Any?) returnType:kotlin.Boolean [fake_override,operator] - overridden: - public open fun equals (other: kotlin.Any?): kotlin.Boolean [fake_override,operator] declared in io.realm.kotlin.types.RealmObject - $this: VALUE_PARAMETER name: type:kotlin.Any - VALUE_PARAMETER name:other index:0 type:kotlin.Any? FUN FAKE_OVERRIDE name:hashCode visibility:public modality:OPEN <> ($this:kotlin.Any) returnType:kotlin.Int [fake_override] overridden: public open fun hashCode (): kotlin.Int [fake_override] declared in io.realm.kotlin.types.RealmObject $this: VALUE_PARAMETER name: type:kotlin.Any - FUN FAKE_OVERRIDE name:toString visibility:public modality:OPEN <> ($this:kotlin.Any) returnType:kotlin.String [fake_override] + FUN name:equals visibility:public modality:OPEN <> ($this:sample.input.Child, other:kotlin.Any?) returnType:kotlin.Int [operator] + overridden: + public open fun equals (other: kotlin.Any?): kotlin.Boolean [fake_override,operator] declared in io.realm.kotlin.types.RealmObject + $this: VALUE_PARAMETER name: type:sample.input.Child + VALUE_PARAMETER name:other index:0 type:kotlin.Any? + BLOCK_BODY + RETURN type=kotlin.Nothing from='public open fun equals (other: kotlin.Any?): kotlin.Int [operator] declared in sample.input.Child' + CALL 'internal final fun realmEquals (obj: io.realm.kotlin.types.BaseRealmObject, other: kotlin.Any?): kotlin.Boolean [inline] declared in io.realm.kotlin.internal.RealmObjectHelper' type=kotlin.Boolean origin=null + $this: GET_OBJECT 'CLASS IR_EXTERNAL_DECLARATION_STUB OBJECT name:RealmObjectHelper modality:FINAL visibility:internal superTypes:[kotlin.Any]' type=io.realm.kotlin.internal.RealmObjectHelper + obj: GET_VAR ': sample.input.Child declared in sample.input.Child.equals' type=sample.input.Child origin=null + other: GET_VAR ': sample.input.Child declared in sample.input.Child.equals' type=sample.input.Child origin=null + FUN name:toString visibility:public modality:OPEN <> ($this:sample.input.Child) returnType:kotlin.String overridden: public open fun toString (): kotlin.String [fake_override] declared in io.realm.kotlin.types.RealmObject - $this: VALUE_PARAMETER name: type:kotlin.Any + $this: VALUE_PARAMETER name: type:sample.input.Child + BLOCK_BODY + RETURN type=kotlin.Nothing from='public open fun toString (): kotlin.String declared in sample.input.Child' + CALL 'internal final fun realmToString (obj: io.realm.kotlin.types.BaseRealmObject): kotlin.String declared in io.realm.kotlin.internal.RealmObjectHelper' type=kotlin.String origin=null + $this: GET_OBJECT 'CLASS IR_EXTERNAL_DECLARATION_STUB OBJECT name:RealmObjectHelper modality:FINAL visibility:internal superTypes:[kotlin.Any]' type=io.realm.kotlin.internal.RealmObjectHelper + obj: GET_VAR ': sample.input.Child declared in sample.input.Child.toString' type=sample.input.Child origin=null PROPERTY name:io_realm_kotlin_objectReference visibility:public modality:OPEN [var] FIELD PROPERTY_BACKING_FIELD name:io_realm_kotlin_objectReference type:io.realm.kotlin.internal.RealmObjectReference? visibility:private EXPRESSION_BODY @@ -9016,19 +9038,30 @@ MODULE_FRAGMENT name:
RETURN type=kotlin.Nothing from='public final fun (): io.realm.kotlin.schema.RealmClassKind declared in sample.input.EmbeddedParent.Companion' GET_FIELD 'FIELD PROPERTY_BACKING_FIELD name:io_realm_kotlin_classKind type:io.realm.kotlin.schema.RealmClassKind visibility:private' type=io.realm.kotlin.schema.RealmClassKind origin=null receiver: GET_VAR ': sample.input.EmbeddedParent.Companion declared in sample.input.EmbeddedParent.Companion.' type=sample.input.EmbeddedParent.Companion origin=null - FUN FAKE_OVERRIDE name:equals visibility:public modality:OPEN <> ($this:kotlin.Any, other:kotlin.Any?) returnType:kotlin.Boolean [fake_override,operator] - overridden: - public open fun equals (other: kotlin.Any?): kotlin.Boolean [fake_override,operator] declared in io.realm.kotlin.types.RealmObject - $this: VALUE_PARAMETER name: type:kotlin.Any - VALUE_PARAMETER name:other index:0 type:kotlin.Any? FUN FAKE_OVERRIDE name:hashCode visibility:public modality:OPEN <> ($this:kotlin.Any) returnType:kotlin.Int [fake_override] overridden: public open fun hashCode (): kotlin.Int [fake_override] declared in io.realm.kotlin.types.RealmObject $this: VALUE_PARAMETER name: type:kotlin.Any - FUN FAKE_OVERRIDE name:toString visibility:public modality:OPEN <> ($this:kotlin.Any) returnType:kotlin.String [fake_override] + FUN name:equals visibility:public modality:OPEN <> ($this:sample.input.EmbeddedParent, other:kotlin.Any?) returnType:kotlin.Int [operator] + overridden: + public open fun equals (other: kotlin.Any?): kotlin.Boolean [fake_override,operator] declared in io.realm.kotlin.types.RealmObject + $this: VALUE_PARAMETER name: type:sample.input.EmbeddedParent + VALUE_PARAMETER name:other index:0 type:kotlin.Any? + BLOCK_BODY + RETURN type=kotlin.Nothing from='public open fun equals (other: kotlin.Any?): kotlin.Int [operator] declared in sample.input.EmbeddedParent' + CALL 'internal final fun realmEquals (obj: io.realm.kotlin.types.BaseRealmObject, other: kotlin.Any?): kotlin.Boolean [inline] declared in io.realm.kotlin.internal.RealmObjectHelper' type=kotlin.Boolean origin=null + $this: GET_OBJECT 'CLASS IR_EXTERNAL_DECLARATION_STUB OBJECT name:RealmObjectHelper modality:FINAL visibility:internal superTypes:[kotlin.Any]' type=io.realm.kotlin.internal.RealmObjectHelper + obj: GET_VAR ': sample.input.EmbeddedParent declared in sample.input.EmbeddedParent.equals' type=sample.input.EmbeddedParent origin=null + other: GET_VAR ': sample.input.EmbeddedParent declared in sample.input.EmbeddedParent.equals' type=sample.input.EmbeddedParent origin=null + FUN name:toString visibility:public modality:OPEN <> ($this:sample.input.EmbeddedParent) returnType:kotlin.String overridden: public open fun toString (): kotlin.String [fake_override] declared in io.realm.kotlin.types.RealmObject - $this: VALUE_PARAMETER name: type:kotlin.Any + $this: VALUE_PARAMETER name: type:sample.input.EmbeddedParent + BLOCK_BODY + RETURN type=kotlin.Nothing from='public open fun toString (): kotlin.String declared in sample.input.EmbeddedParent' + CALL 'internal final fun realmToString (obj: io.realm.kotlin.types.BaseRealmObject): kotlin.String declared in io.realm.kotlin.internal.RealmObjectHelper' type=kotlin.String origin=null + $this: GET_OBJECT 'CLASS IR_EXTERNAL_DECLARATION_STUB OBJECT name:RealmObjectHelper modality:FINAL visibility:internal superTypes:[kotlin.Any]' type=io.realm.kotlin.internal.RealmObjectHelper + obj: GET_VAR ': sample.input.EmbeddedParent declared in sample.input.EmbeddedParent.toString' type=sample.input.EmbeddedParent origin=null PROPERTY name:io_realm_kotlin_objectReference visibility:public modality:OPEN [var] FIELD PROPERTY_BACKING_FIELD name:io_realm_kotlin_objectReference type:io.realm.kotlin.internal.RealmObjectReference? visibility:private EXPRESSION_BODY @@ -9235,19 +9268,30 @@ MODULE_FRAGMENT name:
RETURN type=kotlin.Nothing from='public final fun (): io.realm.kotlin.schema.RealmClassKind declared in sample.input.EmbeddedChild.Companion' GET_FIELD 'FIELD PROPERTY_BACKING_FIELD name:io_realm_kotlin_classKind type:io.realm.kotlin.schema.RealmClassKind visibility:private' type=io.realm.kotlin.schema.RealmClassKind origin=null receiver: GET_VAR ': sample.input.EmbeddedChild.Companion declared in sample.input.EmbeddedChild.Companion.' type=sample.input.EmbeddedChild.Companion origin=null - FUN FAKE_OVERRIDE name:equals visibility:public modality:OPEN <> ($this:kotlin.Any, other:kotlin.Any?) returnType:kotlin.Boolean [fake_override,operator] - overridden: - public open fun equals (other: kotlin.Any?): kotlin.Boolean [fake_override,operator] declared in io.realm.kotlin.types.EmbeddedRealmObject - $this: VALUE_PARAMETER name: type:kotlin.Any - VALUE_PARAMETER name:other index:0 type:kotlin.Any? FUN FAKE_OVERRIDE name:hashCode visibility:public modality:OPEN <> ($this:kotlin.Any) returnType:kotlin.Int [fake_override] overridden: public open fun hashCode (): kotlin.Int [fake_override] declared in io.realm.kotlin.types.EmbeddedRealmObject $this: VALUE_PARAMETER name: type:kotlin.Any - FUN FAKE_OVERRIDE name:toString visibility:public modality:OPEN <> ($this:kotlin.Any) returnType:kotlin.String [fake_override] + FUN name:equals visibility:public modality:OPEN <> ($this:sample.input.EmbeddedChild, other:kotlin.Any?) returnType:kotlin.Int [operator] + overridden: + public open fun equals (other: kotlin.Any?): kotlin.Boolean [fake_override,operator] declared in io.realm.kotlin.types.EmbeddedRealmObject + $this: VALUE_PARAMETER name: type:sample.input.EmbeddedChild + VALUE_PARAMETER name:other index:0 type:kotlin.Any? + BLOCK_BODY + RETURN type=kotlin.Nothing from='public open fun equals (other: kotlin.Any?): kotlin.Int [operator] declared in sample.input.EmbeddedChild' + CALL 'internal final fun realmEquals (obj: io.realm.kotlin.types.BaseRealmObject, other: kotlin.Any?): kotlin.Boolean [inline] declared in io.realm.kotlin.internal.RealmObjectHelper' type=kotlin.Boolean origin=null + $this: GET_OBJECT 'CLASS IR_EXTERNAL_DECLARATION_STUB OBJECT name:RealmObjectHelper modality:FINAL visibility:internal superTypes:[kotlin.Any]' type=io.realm.kotlin.internal.RealmObjectHelper + obj: GET_VAR ': sample.input.EmbeddedChild declared in sample.input.EmbeddedChild.equals' type=sample.input.EmbeddedChild origin=null + other: GET_VAR ': sample.input.EmbeddedChild declared in sample.input.EmbeddedChild.equals' type=sample.input.EmbeddedChild origin=null + FUN name:toString visibility:public modality:OPEN <> ($this:sample.input.EmbeddedChild) returnType:kotlin.String overridden: public open fun toString (): kotlin.String [fake_override] declared in io.realm.kotlin.types.EmbeddedRealmObject - $this: VALUE_PARAMETER name: type:kotlin.Any + $this: VALUE_PARAMETER name: type:sample.input.EmbeddedChild + BLOCK_BODY + RETURN type=kotlin.Nothing from='public open fun toString (): kotlin.String declared in sample.input.EmbeddedChild' + CALL 'internal final fun realmToString (obj: io.realm.kotlin.types.BaseRealmObject): kotlin.String declared in io.realm.kotlin.internal.RealmObjectHelper' type=kotlin.String origin=null + $this: GET_OBJECT 'CLASS IR_EXTERNAL_DECLARATION_STUB OBJECT name:RealmObjectHelper modality:FINAL visibility:internal superTypes:[kotlin.Any]' type=io.realm.kotlin.internal.RealmObjectHelper + obj: GET_VAR ': sample.input.EmbeddedChild declared in sample.input.EmbeddedChild.toString' type=sample.input.EmbeddedChild origin=null PROPERTY name:io_realm_kotlin_objectReference visibility:public modality:OPEN [var] FIELD PROPERTY_BACKING_FIELD name:io_realm_kotlin_objectReference type:io.realm.kotlin.internal.RealmObjectReference? visibility:private EXPRESSION_BODY diff --git a/packages/plugin-compiler/src/test/resources/schema/expected/01_AFTER.ValidateIrBeforeLowering.ir b/packages/plugin-compiler/src/test/resources/schema/expected/01_AFTER.ValidateIrBeforeLowering.ir index 3a6151e3a8..84abd61195 100644 --- a/packages/plugin-compiler/src/test/resources/schema/expected/01_AFTER.ValidateIrBeforeLowering.ir +++ b/packages/plugin-compiler/src/test/resources/schema/expected/01_AFTER.ValidateIrBeforeLowering.ir @@ -127,10 +127,15 @@ MODULE_FRAGMENT name:
overridden: public open fun hashCode (): kotlin.Int [fake_override] declared in io.realm.kotlin.types.RealmObject $this: VALUE_PARAMETER name: type:kotlin.Any - FUN FAKE_OVERRIDE name:toString visibility:public modality:OPEN <> ($this:kotlin.Any) returnType:kotlin.String [fake_override] + FUN name:toString visibility:public modality:OPEN <> ($this:schema.input.A) returnType:kotlin.String overridden: public open fun toString (): kotlin.String [fake_override] declared in io.realm.kotlin.types.RealmObject - $this: VALUE_PARAMETER name: type:kotlin.Any + $this: VALUE_PARAMETER name: type:schema.input.A + BLOCK_BODY + RETURN type=kotlin.Nothing from='public open fun toString (): kotlin.String declared in schema.input.A' + CALL 'internal final fun realmToString (obj: io.realm.kotlin.types.BaseRealmObject): kotlin.String declared in io.realm.kotlin.internal.RealmObjectHelper' type=kotlin.String origin=null + $this: GET_OBJECT 'CLASS IR_EXTERNAL_DECLARATION_STUB OBJECT name:RealmObjectHelper modality:FINAL visibility:internal superTypes:[kotlin.Any]' type=io.realm.kotlin.internal.RealmObjectHelper + obj: GET_VAR ': schema.input.A declared in schema.input.A.toString' type=schema.input.A origin=null PROPERTY name:io_realm_kotlin_objectReference visibility:public modality:OPEN [var] FIELD PROPERTY_BACKING_FIELD name:io_realm_kotlin_objectReference type:io.realm.kotlin.internal.RealmObjectReference? visibility:private EXPRESSION_BODY @@ -280,10 +285,15 @@ MODULE_FRAGMENT name:
overridden: public open fun hashCode (): kotlin.Int [fake_override] declared in io.realm.kotlin.types.RealmObject $this: VALUE_PARAMETER name: type:kotlin.Any - FUN FAKE_OVERRIDE name:toString visibility:public modality:OPEN <> ($this:kotlin.Any) returnType:kotlin.String [fake_override] + FUN name:toString visibility:public modality:OPEN <> ($this:schema.input.B) returnType:kotlin.String overridden: public open fun toString (): kotlin.String [fake_override] declared in io.realm.kotlin.types.RealmObject - $this: VALUE_PARAMETER name: type:kotlin.Any + $this: VALUE_PARAMETER name: type:schema.input.B + BLOCK_BODY + RETURN type=kotlin.Nothing from='public open fun toString (): kotlin.String declared in schema.input.B' + CALL 'internal final fun realmToString (obj: io.realm.kotlin.types.BaseRealmObject): kotlin.String declared in io.realm.kotlin.internal.RealmObjectHelper' type=kotlin.String origin=null + $this: GET_OBJECT 'CLASS IR_EXTERNAL_DECLARATION_STUB OBJECT name:RealmObjectHelper modality:FINAL visibility:internal superTypes:[kotlin.Any]' type=io.realm.kotlin.internal.RealmObjectHelper + obj: GET_VAR ': schema.input.B declared in schema.input.B.toString' type=schema.input.B origin=null PROPERTY name:io_realm_kotlin_objectReference visibility:public modality:OPEN [var] FIELD PROPERTY_BACKING_FIELD name:io_realm_kotlin_objectReference type:io.realm.kotlin.internal.RealmObjectReference? visibility:private EXPRESSION_BODY @@ -433,10 +443,15 @@ MODULE_FRAGMENT name:
overridden: public open fun hashCode (): kotlin.Int [fake_override] declared in io.realm.kotlin.types.RealmObject $this: VALUE_PARAMETER name: type:kotlin.Any - FUN FAKE_OVERRIDE name:toString visibility:public modality:OPEN <> ($this:kotlin.Any) returnType:kotlin.String [fake_override] + FUN name:toString visibility:public modality:OPEN <> ($this:schema.input.C) returnType:kotlin.String overridden: public open fun toString (): kotlin.String [fake_override] declared in io.realm.kotlin.types.RealmObject - $this: VALUE_PARAMETER name: type:kotlin.Any + $this: VALUE_PARAMETER name: type:schema.input.C + BLOCK_BODY + RETURN type=kotlin.Nothing from='public open fun toString (): kotlin.String declared in schema.input.C' + CALL 'internal final fun realmToString (obj: io.realm.kotlin.types.BaseRealmObject): kotlin.String declared in io.realm.kotlin.internal.RealmObjectHelper' type=kotlin.String origin=null + $this: GET_OBJECT 'CLASS IR_EXTERNAL_DECLARATION_STUB OBJECT name:RealmObjectHelper modality:FINAL visibility:internal superTypes:[kotlin.Any]' type=io.realm.kotlin.internal.RealmObjectHelper + obj: GET_VAR ': schema.input.C declared in schema.input.C.toString' type=schema.input.C origin=null PROPERTY name:io_realm_kotlin_objectReference visibility:public modality:OPEN [var] FIELD PROPERTY_BACKING_FIELD name:io_realm_kotlin_objectReference type:io.realm.kotlin.internal.RealmObjectReference? visibility:private EXPRESSION_BODY diff --git a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmObjectTests.kt b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmObjectTests.kt index db044732a7..df162b2592 100644 --- a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmObjectTests.kt +++ b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmObjectTests.kt @@ -113,6 +113,81 @@ class RealmObjectTests : RealmStateTest { // FIXME } + @Test + fun toString_managed() { + assertEquals("BOOM", Parent().toString()) + } + + @Test + fun toString_managed_cyclicData() { + TODO() + } + + @Test + fun toString_customMethod() { + TODO() + } + + @Test + fun toString_managed_invalid() { + TODO() + } + + @Test + fun toString_unmanaged() { + assertEquals(42, Parent().hashCode()) + } + + @Test + fun equals_managed() { + TODO() + } + + @Test + fun equals_managed_cyclicData() { + TODO() + } + + @Test + fun equals_customMethod() { + TODO() + } + + @Test + fun equals_managed_invalid() { + TODO() + } + + @Test + fun equals_unmanaged() { + assertEquals(false, Parent().equals(this)) + } + + @Test + fun hashCode_managed() { + TODO() + } + + @Test + fun hashCode_managed_cyclicData() { + TODO() + } + + @Test + fun hashCode_customMethod() { + TODO() + } + + @Test + fun hashCode_managed_invalid() { + TODO() + } + + @Test + fun hashCode_unmanaged() { + TODO() + } + override fun isFrozen_throwsIfRealmIsClosed() { realm.close() assertFailsWith { From 57b544e1998fc5ea57d251445bb9fc8bbbef5a2c Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 11 Aug 2023 12:24:17 +0200 Subject: [PATCH 2/7] Fix implementations + add most tests --- .../kotlin/internal/RealmObjectHelper.kt | 60 ++++-- .../realm/kotlin/internal/RealmObjectUtil.kt | 9 +- .../kotlin/internal/platform/SystemUtils.kt | 5 + .../kotlin/internal/platform/SystemUtils.kt | 2 + .../kotlin/internal/platform/SystemUtils.kt | 3 + .../RealmModelDefaultMethodGeneration.kt | 40 ++-- .../RealmModelSyntheticMethodsExtension.kt | 14 +- .../01_AFTER.ValidateIrBeforeLowering.ir | 84 +++++---- .../io/realm/kotlin/entities/link/Parent.kt | 1 + .../kotlin/test/common/RealmObjectTests.kt | 172 +++++++++++++++--- 10 files changed, 302 insertions(+), 88 deletions(-) diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmObjectHelper.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmObjectHelper.kt index eca6902752..8ddf3ca1c3 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmObjectHelper.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmObjectHelper.kt @@ -17,9 +17,11 @@ package io.realm.kotlin.internal import io.realm.kotlin.UpdatePolicy +import io.realm.kotlin.VersionId import io.realm.kotlin.dynamic.DynamicMutableRealmObject import io.realm.kotlin.dynamic.DynamicRealmObject import io.realm.kotlin.ext.asRealmObject +import io.realm.kotlin.ext.isValid import io.realm.kotlin.ext.toRealmDictionary import io.realm.kotlin.ext.toRealmList import io.realm.kotlin.ext.toRealmSet @@ -27,6 +29,7 @@ import io.realm.kotlin.internal.dynamic.DynamicUnmanagedRealmObject import io.realm.kotlin.internal.interop.ClassKey import io.realm.kotlin.internal.interop.CollectionType import io.realm.kotlin.internal.interop.MemAllocator +import io.realm.kotlin.internal.interop.ObjectKey import io.realm.kotlin.internal.interop.PropertyKey import io.realm.kotlin.internal.interop.PropertyType import io.realm.kotlin.internal.interop.RealmInterop @@ -39,6 +42,7 @@ import io.realm.kotlin.internal.interop.RealmValue import io.realm.kotlin.internal.interop.Timestamp import io.realm.kotlin.internal.interop.getterScope import io.realm.kotlin.internal.interop.inputScope +import io.realm.kotlin.internal.platform.identityHashCode import io.realm.kotlin.internal.platform.realmObjectCompanionOrThrow import io.realm.kotlin.internal.schema.ClassMetadata import io.realm.kotlin.internal.schema.PropertyMetadata @@ -1145,25 +1149,59 @@ internal object RealmObjectHelper { } } - @OptIn(ExperimentalStdlibApi::class) @Suppress("unused") // Called from generated code + // Inlining this functions somehow break the IntelliJ debugger, unclear why? internal fun realmToString(obj: BaseRealmObject): String { -// if (objReference == null) { -// return "${obj::class.qualifiedName}[Unmanaged]@${hashCode().toHexString()}" -// } else { - -// } - TODO("Custom toString") + // This code assumes no race conditions + val schemaName = obj::class.realmObjectCompanionOrNull()?.io_realm_kotlin_className + val fqName = obj::class.qualifiedName + return obj.realmObjectReference?.let { + if (obj.isValid()) { + val id: Triple = obj.getIdentifier() + val objKey = id.second.key + val version = id.third.version + "$fqName{state=VALID, schemaName=$schemaName, objKey=$objKey, version=$version, realm=${it.owner.owner.configuration.name}}" + } else { + val state = if (it.owner.isClosed()) { + "CLOSED" + } else { + "INVALID" + } + "$fqName{state=$state, schemaName=$schemaName, realm=${it.owner.owner.configuration.path}, hashCode=${obj.hashCode()}}" + } + } ?: "$fqName{state=UNMANAGED, schemaName=$schemaName, hashCode=${obj.hashCode()}}" } @Suppress("unused") // Called from generated code - internal inline fun realmEquals(obj: BaseRealmObject, other: Any?): Boolean { - TODO("BOOM") + // Inlining this functions somehow break the IntelliJ debugger, unclear why? + internal fun realmEquals(obj: BaseRealmObject, other: Any?): Boolean { + if (obj === other) return true + if (other == null || obj::class != other::class) return false + + other as BaseRealmObject + + if (obj.isValid() != other.isValid()) return false + if (obj.getIdentifierOrNull() != other.getIdentifierOrNull()) return false + return (obj.realmObjectReference?.owner?.owner?.configuration?.path == other.realmObjectReference?.owner?.owner?.configuration?.path) } @Suppress("unused") // Called from generated code - internal inline fun realmHashCode(obj: BaseRealmObject): Int { - return 42 + // Inlining this functions somehow break the IntelliJ debugger, unclear why? + internal fun realmHashCode(obj: BaseRealmObject): Int { + // This code assumes no race conditions + return obj.realmObjectReference?.let { + val isValid: Boolean = obj.isValid() + val identifier: Triple = if (it.isClosed()) { + Triple(ClassKey(-1), ObjectKey(-1), VersionId(0)) + } else { + obj.getIdentifier() + } + val realmPath: String = it.owner.owner.configuration.path + var hashCode = isValid.hashCode() + hashCode = 31 * hashCode + identifier.hashCode() + hashCode = 31 * hashCode + realmPath.hashCode() + hashCode + } ?: identityHashCode(obj) } private fun checkPropertyType( diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmObjectUtil.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmObjectUtil.kt index d29843ceaf..5843f3e5e2 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmObjectUtil.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmObjectUtil.kt @@ -144,7 +144,14 @@ internal fun BaseRealmObject.getIdentifier(): RealmObjectIdentifier { val version: VersionId = version() return Triple(classKey, objKey, version) } ?: throw IllegalStateException("Identifier can only be calculated for managed objects.") - ULong +} + +public fun BaseRealmObject.getIdentifierOrNull(): RealmObjectIdentifier? { + return if (realmObjectReference != null) { + getIdentifier() + } else { + null + } } /** diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/platform/SystemUtils.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/platform/SystemUtils.kt index 7091f986d1..32ce083867 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/platform/SystemUtils.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/platform/SystemUtils.kt @@ -145,3 +145,8 @@ public expect fun returnType(field: KMutableProperty1 * Returns whether or not we are running on Windows */ public expect fun isWindows(): Boolean + +/** + * Returns the identity hashcode for a given object. + */ +internal expect fun identityHashCode(obj: Any?): Int diff --git a/packages/library-base/src/jvm/kotlin/io/realm/kotlin/internal/platform/SystemUtils.kt b/packages/library-base/src/jvm/kotlin/io/realm/kotlin/internal/platform/SystemUtils.kt index 8814ab803e..11ed3e1512 100644 --- a/packages/library-base/src/jvm/kotlin/io/realm/kotlin/internal/platform/SystemUtils.kt +++ b/packages/library-base/src/jvm/kotlin/io/realm/kotlin/internal/platform/SystemUtils.kt @@ -124,3 +124,5 @@ private fun preparePath(directoryPath: String) { } public actual fun isWindows(): Boolean = OS_NAME.contains("windows", ignoreCase = true) + +internal actual fun identityHashCode(obj: Any?): Int = System.identityHashCode(obj) diff --git a/packages/library-base/src/nativeDarwin/kotlin/io/realm/kotlin/internal/platform/SystemUtils.kt b/packages/library-base/src/nativeDarwin/kotlin/io/realm/kotlin/internal/platform/SystemUtils.kt index b71e553187..c1fff3dedd 100644 --- a/packages/library-base/src/nativeDarwin/kotlin/io/realm/kotlin/internal/platform/SystemUtils.kt +++ b/packages/library-base/src/nativeDarwin/kotlin/io/realm/kotlin/internal/platform/SystemUtils.kt @@ -29,6 +29,7 @@ import platform.Foundation.dataWithContentsOfFile import platform.Foundation.timeIntervalSince1970 import platform.posix.memcpy import platform.posix.pthread_threadid_np +import kotlin.native.identityHashCode import kotlin.reflect.KMutableProperty1 import kotlin.reflect.KType @@ -192,3 +193,5 @@ private fun NSData.toByteArray(): ByteArray = ByteArray(this@toByteArray.length. } public actual fun isWindows(): Boolean = false + +internal actual fun identityHashCode(obj: Any?): Int = obj.identityHashCode() diff --git a/packages/plugin-compiler/src/main/kotlin/io/realm/kotlin/compiler/RealmModelDefaultMethodGeneration.kt b/packages/plugin-compiler/src/main/kotlin/io/realm/kotlin/compiler/RealmModelDefaultMethodGeneration.kt index 97fad72e3b..0ffec66ccf 100644 --- a/packages/plugin-compiler/src/main/kotlin/io/realm/kotlin/compiler/RealmModelDefaultMethodGeneration.kt +++ b/packages/plugin-compiler/src/main/kotlin/io/realm/kotlin/compiler/RealmModelDefaultMethodGeneration.kt @@ -17,6 +17,14 @@ import org.jetbrains.kotlin.name.Name * - toString() * - hashCode() * - equals() + * + * WARNING: The current logic in here does not work well with incremental compilation. The reason + * is that we check if these methods are "empty" before filling them out, and during incremental + * compilation they already have content, and since all of these methods are using inlined + * methods they will not pick up changes in the RealmObjectHelper. + * + * This should only impact us as SDK developers though, but it does mean that changes to + * RealmObjectHelper methods will require a clean build to take effect. */ class RealmModelDefaultMethodGeneration(private val pluginContext: IrPluginContext) { @@ -69,7 +77,7 @@ class RealmModelDefaultMethodGeneration(private val pluginContext: IrPluginConte ).apply { dispatchReceiver = irGetObject(realmObjectHelper.symbol) putValueArgument(0, irGet(function.dispatchReceiverParameter!!.type, function.dispatchReceiverParameter!!.symbol)) - putValueArgument(1, irGet(function.dispatchReceiverParameter!!.type, function.dispatchReceiverParameter!!.symbol)) + putValueArgument(1, irGet(function.valueParameters[0].type, function.valueParameters[0].symbol)) } ) } @@ -97,19 +105,19 @@ class RealmModelDefaultMethodGeneration(private val pluginContext: IrPluginConte private fun addToStringMethodBody(irClass: IrClass) { val function: IrSimpleFunction = irClass.symbol.owner.functions.single { it.name.toString() == "toString" } function.body = pluginContext.blockBody(function.symbol) { - +irReturn( - IrCallImpl( - startOffset = startOffset, - endOffset = endOffset, - type = pluginContext.irBuiltIns.stringType, - symbol = realmToString.symbol, - typeArgumentsCount = 0, - valueArgumentsCount = 1 - ).apply { - dispatchReceiver = irGetObject(realmObjectHelper.symbol) - putValueArgument(0, irGet(function.dispatchReceiverParameter!!.type, function.dispatchReceiverParameter!!.symbol)) - } - ) - } + +irReturn( + IrCallImpl( + startOffset = startOffset, + endOffset = endOffset, + type = pluginContext.irBuiltIns.stringType, + symbol = realmToString.symbol, + typeArgumentsCount = 0, + valueArgumentsCount = 1 + ).apply { + dispatchReceiver = irGetObject(realmObjectHelper.symbol) + putValueArgument(0, irGet(function.dispatchReceiverParameter!!.type, function.dispatchReceiverParameter!!.symbol)) + } + ) + } } -} \ No newline at end of file +} diff --git a/packages/plugin-compiler/src/main/kotlin/io/realm/kotlin/compiler/RealmModelSyntheticMethodsExtension.kt b/packages/plugin-compiler/src/main/kotlin/io/realm/kotlin/compiler/RealmModelSyntheticMethodsExtension.kt index 27318e393a..7146e68915 100644 --- a/packages/plugin-compiler/src/main/kotlin/io/realm/kotlin/compiler/RealmModelSyntheticMethodsExtension.kt +++ b/packages/plugin-compiler/src/main/kotlin/io/realm/kotlin/compiler/RealmModelSyntheticMethodsExtension.kt @@ -44,13 +44,13 @@ class RealmModelSyntheticMethodsExtension : SyntheticResolveExtension { fromSupertypes: List, result: MutableCollection ) { - if (thisDescriptor.isRealmObject - && !thisDescriptor.isCompanionObject /* Do not override companion object methods */ - && !thisDescriptor.isInner /* Do not override inner class methods */ - && !isNestedInRealmModelClass(thisDescriptor) /* do not override nested class methods */ - && result.isEmpty() /* = no method has been declared in the current class */ + if (thisDescriptor.isRealmObject && + !thisDescriptor.isCompanionObject && /* Do not override companion object methods */ + !thisDescriptor.isInner && /* Do not override inner class methods */ + !isNestedInRealmModelClass(thisDescriptor) && /* do not override nested class methods */ + result.isEmpty() /* = no method has been declared in the current class */ ) { - when(name.identifier) { + when (name.identifier) { "toString" -> { result.add( createMethod( @@ -127,7 +127,7 @@ class RealmModelSyntheticMethodsExtension : SyntheticResolveExtension { private fun isNestedInRealmModelClass(classDescriptor: ClassDescriptor): Boolean { return classDescriptor.parents.firstOrNull { - if (it is ClassDescriptor) { + return if (it is ClassDescriptor) { it.isRealmObject } else { false diff --git a/packages/plugin-compiler/src/test/resources/sample/expected/01_AFTER.ValidateIrBeforeLowering.ir b/packages/plugin-compiler/src/test/resources/sample/expected/01_AFTER.ValidateIrBeforeLowering.ir index f39438d3f0..3ca90d16e5 100644 --- a/packages/plugin-compiler/src/test/resources/sample/expected/01_AFTER.ValidateIrBeforeLowering.ir +++ b/packages/plugin-compiler/src/test/resources/sample/expected/01_AFTER.ValidateIrBeforeLowering.ir @@ -8508,21 +8508,26 @@ MODULE_FRAGMENT name:
RETURN type=kotlin.Nothing from='public final fun (): io.realm.kotlin.schema.RealmClassKind declared in sample.input.Sample.Companion' GET_FIELD 'FIELD PROPERTY_BACKING_FIELD name:io_realm_kotlin_classKind type:io.realm.kotlin.schema.RealmClassKind visibility:private' type=io.realm.kotlin.schema.RealmClassKind origin=null receiver: GET_VAR ': sample.input.Sample.Companion declared in sample.input.Sample.Companion.' type=sample.input.Sample.Companion origin=null - FUN FAKE_OVERRIDE name:hashCode visibility:public modality:OPEN <> ($this:kotlin.Any) returnType:kotlin.Int [fake_override] - overridden: - public open fun hashCode (): kotlin.Int [fake_override] declared in io.realm.kotlin.types.RealmObject - $this: VALUE_PARAMETER name: type:kotlin.Any - FUN name:equals visibility:public modality:OPEN <> ($this:sample.input.Sample, other:kotlin.Any?) returnType:kotlin.Int [operator] + FUN name:equals visibility:public modality:OPEN <> ($this:sample.input.Sample, other:kotlin.Any?) returnType:kotlin.Boolean [operator] overridden: public open fun equals (other: kotlin.Any?): kotlin.Boolean [fake_override,operator] declared in io.realm.kotlin.types.RealmObject $this: VALUE_PARAMETER name: type:sample.input.Sample VALUE_PARAMETER name:other index:0 type:kotlin.Any? BLOCK_BODY - RETURN type=kotlin.Nothing from='public open fun equals (other: kotlin.Any?): kotlin.Int [operator] declared in sample.input.Sample' - CALL 'internal final fun realmEquals (obj: io.realm.kotlin.types.BaseRealmObject, other: kotlin.Any?): kotlin.Boolean [inline] declared in io.realm.kotlin.internal.RealmObjectHelper' type=kotlin.Boolean origin=null + RETURN type=kotlin.Nothing from='public open fun equals (other: kotlin.Any?): kotlin.Boolean [operator] declared in sample.input.Sample' + CALL 'internal final fun realmEquals (obj: io.realm.kotlin.types.BaseRealmObject, other: kotlin.Any?): kotlin.Boolean declared in io.realm.kotlin.internal.RealmObjectHelper' type=kotlin.Boolean origin=null $this: GET_OBJECT 'CLASS IR_EXTERNAL_DECLARATION_STUB OBJECT name:RealmObjectHelper modality:FINAL visibility:internal superTypes:[kotlin.Any]' type=io.realm.kotlin.internal.RealmObjectHelper obj: GET_VAR ': sample.input.Sample declared in sample.input.Sample.equals' type=sample.input.Sample origin=null - other: GET_VAR ': sample.input.Sample declared in sample.input.Sample.equals' type=sample.input.Sample origin=null + other: GET_VAR 'other: kotlin.Any? declared in sample.input.Sample.equals' type=kotlin.Any? origin=null + FUN name:hashCode visibility:public modality:OPEN <> ($this:sample.input.Sample) returnType:kotlin.Int + overridden: + public open fun hashCode (): kotlin.Int [fake_override] declared in io.realm.kotlin.types.RealmObject + $this: VALUE_PARAMETER name: type:sample.input.Sample + BLOCK_BODY + RETURN type=kotlin.Nothing from='public open fun hashCode (): kotlin.Int declared in sample.input.Sample' + CALL 'internal final fun realmHashCode (obj: io.realm.kotlin.types.BaseRealmObject): kotlin.Int declared in io.realm.kotlin.internal.RealmObjectHelper' type=kotlin.Int origin=null + $this: GET_OBJECT 'CLASS IR_EXTERNAL_DECLARATION_STUB OBJECT name:RealmObjectHelper modality:FINAL visibility:internal superTypes:[kotlin.Any]' type=io.realm.kotlin.internal.RealmObjectHelper + obj: GET_VAR ': sample.input.Sample declared in sample.input.Sample.hashCode' type=sample.input.Sample origin=null FUN name:toString visibility:public modality:OPEN <> ($this:sample.input.Sample) returnType:kotlin.String overridden: public open fun toString (): kotlin.String [fake_override] declared in io.realm.kotlin.types.RealmObject @@ -8806,21 +8811,26 @@ MODULE_FRAGMENT name:
RETURN type=kotlin.Nothing from='public final fun (): io.realm.kotlin.schema.RealmClassKind declared in sample.input.Child.Companion' GET_FIELD 'FIELD PROPERTY_BACKING_FIELD name:io_realm_kotlin_classKind type:io.realm.kotlin.schema.RealmClassKind visibility:private' type=io.realm.kotlin.schema.RealmClassKind origin=null receiver: GET_VAR ': sample.input.Child.Companion declared in sample.input.Child.Companion.' type=sample.input.Child.Companion origin=null - FUN FAKE_OVERRIDE name:hashCode visibility:public modality:OPEN <> ($this:kotlin.Any) returnType:kotlin.Int [fake_override] - overridden: - public open fun hashCode (): kotlin.Int [fake_override] declared in io.realm.kotlin.types.RealmObject - $this: VALUE_PARAMETER name: type:kotlin.Any - FUN name:equals visibility:public modality:OPEN <> ($this:sample.input.Child, other:kotlin.Any?) returnType:kotlin.Int [operator] + FUN name:equals visibility:public modality:OPEN <> ($this:sample.input.Child, other:kotlin.Any?) returnType:kotlin.Boolean [operator] overridden: public open fun equals (other: kotlin.Any?): kotlin.Boolean [fake_override,operator] declared in io.realm.kotlin.types.RealmObject $this: VALUE_PARAMETER name: type:sample.input.Child VALUE_PARAMETER name:other index:0 type:kotlin.Any? BLOCK_BODY - RETURN type=kotlin.Nothing from='public open fun equals (other: kotlin.Any?): kotlin.Int [operator] declared in sample.input.Child' - CALL 'internal final fun realmEquals (obj: io.realm.kotlin.types.BaseRealmObject, other: kotlin.Any?): kotlin.Boolean [inline] declared in io.realm.kotlin.internal.RealmObjectHelper' type=kotlin.Boolean origin=null + RETURN type=kotlin.Nothing from='public open fun equals (other: kotlin.Any?): kotlin.Boolean [operator] declared in sample.input.Child' + CALL 'internal final fun realmEquals (obj: io.realm.kotlin.types.BaseRealmObject, other: kotlin.Any?): kotlin.Boolean declared in io.realm.kotlin.internal.RealmObjectHelper' type=kotlin.Boolean origin=null $this: GET_OBJECT 'CLASS IR_EXTERNAL_DECLARATION_STUB OBJECT name:RealmObjectHelper modality:FINAL visibility:internal superTypes:[kotlin.Any]' type=io.realm.kotlin.internal.RealmObjectHelper obj: GET_VAR ': sample.input.Child declared in sample.input.Child.equals' type=sample.input.Child origin=null - other: GET_VAR ': sample.input.Child declared in sample.input.Child.equals' type=sample.input.Child origin=null + other: GET_VAR 'other: kotlin.Any? declared in sample.input.Child.equals' type=kotlin.Any? origin=null + FUN name:hashCode visibility:public modality:OPEN <> ($this:sample.input.Child) returnType:kotlin.Int + overridden: + public open fun hashCode (): kotlin.Int [fake_override] declared in io.realm.kotlin.types.RealmObject + $this: VALUE_PARAMETER name: type:sample.input.Child + BLOCK_BODY + RETURN type=kotlin.Nothing from='public open fun hashCode (): kotlin.Int declared in sample.input.Child' + CALL 'internal final fun realmHashCode (obj: io.realm.kotlin.types.BaseRealmObject): kotlin.Int declared in io.realm.kotlin.internal.RealmObjectHelper' type=kotlin.Int origin=null + $this: GET_OBJECT 'CLASS IR_EXTERNAL_DECLARATION_STUB OBJECT name:RealmObjectHelper modality:FINAL visibility:internal superTypes:[kotlin.Any]' type=io.realm.kotlin.internal.RealmObjectHelper + obj: GET_VAR ': sample.input.Child declared in sample.input.Child.hashCode' type=sample.input.Child origin=null FUN name:toString visibility:public modality:OPEN <> ($this:sample.input.Child) returnType:kotlin.String overridden: public open fun toString (): kotlin.String [fake_override] declared in io.realm.kotlin.types.RealmObject @@ -9038,21 +9048,26 @@ MODULE_FRAGMENT name:
RETURN type=kotlin.Nothing from='public final fun (): io.realm.kotlin.schema.RealmClassKind declared in sample.input.EmbeddedParent.Companion' GET_FIELD 'FIELD PROPERTY_BACKING_FIELD name:io_realm_kotlin_classKind type:io.realm.kotlin.schema.RealmClassKind visibility:private' type=io.realm.kotlin.schema.RealmClassKind origin=null receiver: GET_VAR ': sample.input.EmbeddedParent.Companion declared in sample.input.EmbeddedParent.Companion.' type=sample.input.EmbeddedParent.Companion origin=null - FUN FAKE_OVERRIDE name:hashCode visibility:public modality:OPEN <> ($this:kotlin.Any) returnType:kotlin.Int [fake_override] - overridden: - public open fun hashCode (): kotlin.Int [fake_override] declared in io.realm.kotlin.types.RealmObject - $this: VALUE_PARAMETER name: type:kotlin.Any - FUN name:equals visibility:public modality:OPEN <> ($this:sample.input.EmbeddedParent, other:kotlin.Any?) returnType:kotlin.Int [operator] + FUN name:equals visibility:public modality:OPEN <> ($this:sample.input.EmbeddedParent, other:kotlin.Any?) returnType:kotlin.Boolean [operator] overridden: public open fun equals (other: kotlin.Any?): kotlin.Boolean [fake_override,operator] declared in io.realm.kotlin.types.RealmObject $this: VALUE_PARAMETER name: type:sample.input.EmbeddedParent VALUE_PARAMETER name:other index:0 type:kotlin.Any? BLOCK_BODY - RETURN type=kotlin.Nothing from='public open fun equals (other: kotlin.Any?): kotlin.Int [operator] declared in sample.input.EmbeddedParent' - CALL 'internal final fun realmEquals (obj: io.realm.kotlin.types.BaseRealmObject, other: kotlin.Any?): kotlin.Boolean [inline] declared in io.realm.kotlin.internal.RealmObjectHelper' type=kotlin.Boolean origin=null + RETURN type=kotlin.Nothing from='public open fun equals (other: kotlin.Any?): kotlin.Boolean [operator] declared in sample.input.EmbeddedParent' + CALL 'internal final fun realmEquals (obj: io.realm.kotlin.types.BaseRealmObject, other: kotlin.Any?): kotlin.Boolean declared in io.realm.kotlin.internal.RealmObjectHelper' type=kotlin.Boolean origin=null $this: GET_OBJECT 'CLASS IR_EXTERNAL_DECLARATION_STUB OBJECT name:RealmObjectHelper modality:FINAL visibility:internal superTypes:[kotlin.Any]' type=io.realm.kotlin.internal.RealmObjectHelper obj: GET_VAR ': sample.input.EmbeddedParent declared in sample.input.EmbeddedParent.equals' type=sample.input.EmbeddedParent origin=null - other: GET_VAR ': sample.input.EmbeddedParent declared in sample.input.EmbeddedParent.equals' type=sample.input.EmbeddedParent origin=null + other: GET_VAR 'other: kotlin.Any? declared in sample.input.EmbeddedParent.equals' type=kotlin.Any? origin=null + FUN name:hashCode visibility:public modality:OPEN <> ($this:sample.input.EmbeddedParent) returnType:kotlin.Int + overridden: + public open fun hashCode (): kotlin.Int [fake_override] declared in io.realm.kotlin.types.RealmObject + $this: VALUE_PARAMETER name: type:sample.input.EmbeddedParent + BLOCK_BODY + RETURN type=kotlin.Nothing from='public open fun hashCode (): kotlin.Int declared in sample.input.EmbeddedParent' + CALL 'internal final fun realmHashCode (obj: io.realm.kotlin.types.BaseRealmObject): kotlin.Int declared in io.realm.kotlin.internal.RealmObjectHelper' type=kotlin.Int origin=null + $this: GET_OBJECT 'CLASS IR_EXTERNAL_DECLARATION_STUB OBJECT name:RealmObjectHelper modality:FINAL visibility:internal superTypes:[kotlin.Any]' type=io.realm.kotlin.internal.RealmObjectHelper + obj: GET_VAR ': sample.input.EmbeddedParent declared in sample.input.EmbeddedParent.hashCode' type=sample.input.EmbeddedParent origin=null FUN name:toString visibility:public modality:OPEN <> ($this:sample.input.EmbeddedParent) returnType:kotlin.String overridden: public open fun toString (): kotlin.String [fake_override] declared in io.realm.kotlin.types.RealmObject @@ -9268,21 +9283,26 @@ MODULE_FRAGMENT name:
RETURN type=kotlin.Nothing from='public final fun (): io.realm.kotlin.schema.RealmClassKind declared in sample.input.EmbeddedChild.Companion' GET_FIELD 'FIELD PROPERTY_BACKING_FIELD name:io_realm_kotlin_classKind type:io.realm.kotlin.schema.RealmClassKind visibility:private' type=io.realm.kotlin.schema.RealmClassKind origin=null receiver: GET_VAR ': sample.input.EmbeddedChild.Companion declared in sample.input.EmbeddedChild.Companion.' type=sample.input.EmbeddedChild.Companion origin=null - FUN FAKE_OVERRIDE name:hashCode visibility:public modality:OPEN <> ($this:kotlin.Any) returnType:kotlin.Int [fake_override] - overridden: - public open fun hashCode (): kotlin.Int [fake_override] declared in io.realm.kotlin.types.EmbeddedRealmObject - $this: VALUE_PARAMETER name: type:kotlin.Any - FUN name:equals visibility:public modality:OPEN <> ($this:sample.input.EmbeddedChild, other:kotlin.Any?) returnType:kotlin.Int [operator] + FUN name:equals visibility:public modality:OPEN <> ($this:sample.input.EmbeddedChild, other:kotlin.Any?) returnType:kotlin.Boolean [operator] overridden: public open fun equals (other: kotlin.Any?): kotlin.Boolean [fake_override,operator] declared in io.realm.kotlin.types.EmbeddedRealmObject $this: VALUE_PARAMETER name: type:sample.input.EmbeddedChild VALUE_PARAMETER name:other index:0 type:kotlin.Any? BLOCK_BODY - RETURN type=kotlin.Nothing from='public open fun equals (other: kotlin.Any?): kotlin.Int [operator] declared in sample.input.EmbeddedChild' - CALL 'internal final fun realmEquals (obj: io.realm.kotlin.types.BaseRealmObject, other: kotlin.Any?): kotlin.Boolean [inline] declared in io.realm.kotlin.internal.RealmObjectHelper' type=kotlin.Boolean origin=null + RETURN type=kotlin.Nothing from='public open fun equals (other: kotlin.Any?): kotlin.Boolean [operator] declared in sample.input.EmbeddedChild' + CALL 'internal final fun realmEquals (obj: io.realm.kotlin.types.BaseRealmObject, other: kotlin.Any?): kotlin.Boolean declared in io.realm.kotlin.internal.RealmObjectHelper' type=kotlin.Boolean origin=null $this: GET_OBJECT 'CLASS IR_EXTERNAL_DECLARATION_STUB OBJECT name:RealmObjectHelper modality:FINAL visibility:internal superTypes:[kotlin.Any]' type=io.realm.kotlin.internal.RealmObjectHelper obj: GET_VAR ': sample.input.EmbeddedChild declared in sample.input.EmbeddedChild.equals' type=sample.input.EmbeddedChild origin=null - other: GET_VAR ': sample.input.EmbeddedChild declared in sample.input.EmbeddedChild.equals' type=sample.input.EmbeddedChild origin=null + other: GET_VAR 'other: kotlin.Any? declared in sample.input.EmbeddedChild.equals' type=kotlin.Any? origin=null + FUN name:hashCode visibility:public modality:OPEN <> ($this:sample.input.EmbeddedChild) returnType:kotlin.Int + overridden: + public open fun hashCode (): kotlin.Int [fake_override] declared in io.realm.kotlin.types.EmbeddedRealmObject + $this: VALUE_PARAMETER name: type:sample.input.EmbeddedChild + BLOCK_BODY + RETURN type=kotlin.Nothing from='public open fun hashCode (): kotlin.Int declared in sample.input.EmbeddedChild' + CALL 'internal final fun realmHashCode (obj: io.realm.kotlin.types.BaseRealmObject): kotlin.Int declared in io.realm.kotlin.internal.RealmObjectHelper' type=kotlin.Int origin=null + $this: GET_OBJECT 'CLASS IR_EXTERNAL_DECLARATION_STUB OBJECT name:RealmObjectHelper modality:FINAL visibility:internal superTypes:[kotlin.Any]' type=io.realm.kotlin.internal.RealmObjectHelper + obj: GET_VAR ': sample.input.EmbeddedChild declared in sample.input.EmbeddedChild.hashCode' type=sample.input.EmbeddedChild origin=null FUN name:toString visibility:public modality:OPEN <> ($this:sample.input.EmbeddedChild) returnType:kotlin.String overridden: public open fun toString (): kotlin.String [fake_override] declared in io.realm.kotlin.types.EmbeddedRealmObject diff --git a/packages/test-base/src/commonMain/kotlin/io/realm/kotlin/entities/link/Parent.kt b/packages/test-base/src/commonMain/kotlin/io/realm/kotlin/entities/link/Parent.kt index 45675d7a3f..f77d627760 100644 --- a/packages/test-base/src/commonMain/kotlin/io/realm/kotlin/entities/link/Parent.kt +++ b/packages/test-base/src/commonMain/kotlin/io/realm/kotlin/entities/link/Parent.kt @@ -21,4 +21,5 @@ import io.realm.kotlin.types.RealmObject class Parent : RealmObject { var name: String = "N.N." var child: Child? = null + var otherParent: Parent? = null } diff --git a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmObjectTests.kt b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmObjectTests.kt index df162b2592..5f4eb1487f 100644 --- a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmObjectTests.kt +++ b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmObjectTests.kt @@ -25,6 +25,7 @@ import io.realm.kotlin.ext.isValid import io.realm.kotlin.ext.version import io.realm.kotlin.test.common.utils.RealmStateTest import io.realm.kotlin.test.platform.PlatformUtils +import io.realm.kotlin.types.RealmObject import kotlin.test.AfterTest import kotlin.test.BeforeTest import kotlin.test.Ignore @@ -32,8 +33,34 @@ import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFailsWith import kotlin.test.assertFalse +import kotlin.test.assertNotEquals import kotlin.test.assertTrue +// Model class with toString/equals/hashCode +class CustomMethods : RealmObject { + var name: String = "" + var age: Int = 0 + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other == null || this::class != other::class) return false + other as CustomMethods + return this.age == 42 && other.age == 42 + } + + override fun hashCode(): Int { + return if (isValid()) { + 42 + } else { + -1 + } + } + + override fun toString(): String { + return "customToString" + } +} + class RealmObjectTests : RealmStateTest { companion object { @@ -48,7 +75,7 @@ class RealmObjectTests : RealmStateTest { @BeforeTest fun setup() { tmpDir = PlatformUtils.createTempDir() - val configuration = RealmConfiguration.Builder(schema = setOf(Parent::class, Child::class)) + val configuration = RealmConfiguration.Builder(schema = setOf(Parent::class, Child::class, CustomMethods::class)) .directory(tmpDir) .build() realm = Realm.open(configuration) @@ -115,77 +142,180 @@ class RealmObjectTests : RealmStateTest { @Test fun toString_managed() { - assertEquals("BOOM", Parent().toString()) + val managedObj = realm.writeBlocking { + copyToRealm(Parent()) + } + val regex = Regex("io.realm.kotlin.entities.link.Parent\\{state=VALID, schemaName=Parent, objKey=[0-9]*, version=[0-9]*, realm=${realm.configuration.path}}") + assertTrue(regex.matches(managedObj.toString()), managedObj.toString()) } @Test fun toString_managed_cyclicData() { - TODO() + val p1 = Parent() + p1.name = "Parent" + p1.otherParent = p1 + val managedObj = realm.writeBlocking { copyToRealm(p1) } + val regex = Regex("io.realm.kotlin.entities.link.Parent\\{state=VALID, schemaName=Parent, objKey=[0-9]*, version=[0-9]*, realm=${realm.configuration.path}}") + assertTrue(regex.matches(managedObj.toString()), managedObj.toString()) } @Test - fun toString_customMethod() { - TODO() + fun toString_managed_invalid() { + realm.writeBlocking { + val managedObject = copyToRealm(Parent()) + delete(managedObject) + val regex = Regex("io.realm.kotlin.entities.link.Parent\\{state=INVALID, schemaName=Parent, realm=${realm.configuration.path}, hashCode=[0-9]*\\}") + assertTrue(regex.matches(managedObject.toString()), managedObject.toString()) + cancelWrite() + } } @Test - fun toString_managed_invalid() { - TODO() + fun toString_managed_closedRealm() { + val managedObject = realm.writeBlocking { + copyToRealm(Parent()) + } + realm.close() + val regex = Regex("io.realm.kotlin.entities.link.Parent\\{state=CLOSED, schemaName=Parent, realm=${realm.configuration.path}, hashCode=[0-9]*\\}") + assertTrue(regex.matches(managedObject.toString()), managedObject.toString()) + } + + @Test + fun toString_customMethod() { + assertEquals("customToString", CustomMethods().toString()) + val managedObj = realm.writeBlocking { copyToRealm(CustomMethods()) } + assertEquals("customToString", managedObj.toString()) } @Test fun toString_unmanaged() { - assertEquals(42, Parent().hashCode()) + val unmanagedObject = Parent() + val regex = Regex("io.realm.kotlin.entities.link.Parent\\{state=UNMANAGED, schemaName=Parent, hashCode=[0-9]*\\}") + assertTrue(regex.matches(unmanagedObject.toString()), unmanagedObject.toString()) } @Test fun equals_managed() { - TODO() + realm.writeBlocking { + val p1 = copyToRealm(Parent().apply { this.name = "Jane" }) + val p2 = copyToRealm(Parent()) + val p3 = query(Parent::class, "name = 'Jane'").first().find()!! + assertEquals(p1, p1) + assertEquals(p1, p3) + assertEquals(p1.hashCode(), p1.hashCode()) + assertEquals(p1.hashCode(), p3.hashCode()) + + // Not restrictions are given on hashCode if two objects are not equal + assertNotEquals(p2, p1) + assertNotEquals(p2, p3) + } + } + + @Test + fun equals_unmanaged() { + val p1 = Parent() + val p2 = Parent() + assertEquals(p1, p1) + assertEquals(p1.hashCode(), p1.hashCode()) + assertNotEquals(p1, p2) + } + + @Test + fun equals_mixed() { + val unmanagedObj = Parent() + val managedObj = realm.writeBlocking { copyToRealm(Parent()) } + assertNotEquals(unmanagedObj, managedObj) + assertNotEquals(managedObj, unmanagedObj) } @Test fun equals_managed_cyclicData() { - TODO() + realm.writeBlocking { + val p1 = copyToRealm(Parent().apply { this.name = "Jane" }) + p1.otherParent = p1 + val p2 = copyToRealm(Parent()) + val p3 = query(Parent::class, "name = 'Jane'").first().find()!! + assertEquals(p1, p1) + assertEquals(p1, p3) + assertEquals(p1.hashCode(), p1.hashCode()) + assertEquals(p1.hashCode(), p3.hashCode()) + + // Not restrictions are given on hashCode if two objects are not equal + assertNotEquals(p2, p1) + assertNotEquals(p2, p3) + } } @Test fun equals_customMethod() { - TODO() + // Only equals if age = 42 or same instance + val obj1 = CustomMethods() + val obj2 = CustomMethods() + assertEquals(obj1, obj1) + assertEquals(obj1.hashCode(), obj1.hashCode()) + assertNotEquals(obj1, obj2) + + val obj3 = CustomMethods().apply { age = 42 } + val obj4 = CustomMethods().apply { age = 42 } + assertEquals(obj3, obj3) + assertEquals(obj3.hashCode(), obj4.hashCode()) + assertEquals(obj3, obj4) + assertEquals(obj3.hashCode(), obj4.hashCode()) + + // Managed objects + realm.writeBlocking { + val obj1 = copyToRealm(CustomMethods()) + val obj2 = copyToRealm(CustomMethods()) + assertEquals(obj1, obj1) + assertEquals(obj1.hashCode(), obj1.hashCode()) + assertNotEquals(obj1, obj2) + + val obj3 = copyToRealm(CustomMethods().apply { age = 42 }) + val obj4 = copyToRealm(CustomMethods().apply { age = 42 }) + assertEquals(obj3, obj3) + assertEquals(obj3.hashCode(), obj3.hashCode()) + assertEquals(obj3, obj4) + assertEquals(obj3.hashCode(), obj4.hashCode()) + } } @Test fun equals_managed_invalid() { - TODO() - } + realm.writeBlocking { + val p1 = copyToRealm(Parent().apply { this.name = "Jane" }) + val p2 = copyToRealm(Parent()) + delete(p1) + delete(p2) - @Test - fun equals_unmanaged() { - assertEquals(false, Parent().equals(this)) + assertEquals(p1, p1) + assertEquals(p1.hashCode(), p1.hashCode()) + assertNotEquals(p1, p2) + } } @Test fun hashCode_managed() { - TODO() +// TODO() } @Test fun hashCode_managed_cyclicData() { - TODO() +// TODO() } @Test fun hashCode_customMethod() { - TODO() +// TODO() } @Test fun hashCode_managed_invalid() { - TODO() +// TODO() } @Test fun hashCode_unmanaged() { - TODO() +// TODO() } override fun isFrozen_throwsIfRealmIsClosed() { From 72dd8ff2dc85d0926551ea2c19d1a0d540e4bc37 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Mon, 14 Aug 2023 07:37:08 +0200 Subject: [PATCH 3/7] Fix RealmList/RealmSet tests --- .../kotlin/internal/RealmObjectHelper.kt | 19 ++++--- .../RealmModelSyntheticMethodsExtension.kt | 1 + .../kotlin/test/common/RealmListTests.kt | 52 ++++++++++++++++++- .../kotlin/test/common/RealmObjectTests.kt | 51 ++++++------------ .../realm/kotlin/test/common/RealmSetTests.kt | 30 +++++------ 5 files changed, 94 insertions(+), 59 deletions(-) diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmObjectHelper.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmObjectHelper.kt index 8ddf3ca1c3..6936b9e3f0 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmObjectHelper.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmObjectHelper.kt @@ -21,6 +21,7 @@ import io.realm.kotlin.VersionId import io.realm.kotlin.dynamic.DynamicMutableRealmObject import io.realm.kotlin.dynamic.DynamicRealmObject import io.realm.kotlin.ext.asRealmObject +import io.realm.kotlin.ext.isManaged import io.realm.kotlin.ext.isValid import io.realm.kotlin.ext.toRealmDictionary import io.realm.kotlin.ext.toRealmList @@ -1167,12 +1168,12 @@ internal object RealmObjectHelper { } else { "INVALID" } - "$fqName{state=$state, schemaName=$schemaName, realm=${it.owner.owner.configuration.path}, hashCode=${obj.hashCode()}}" + "$fqName{state=$state, schemaName=$schemaName, realm=${it.owner.owner.configuration.name}, hashCode=${obj.hashCode()}}" } } ?: "$fqName{state=UNMANAGED, schemaName=$schemaName, hashCode=${obj.hashCode()}}" } - @Suppress("unused") // Called from generated code + @Suppress("unused", "ReturnCount") // Called from generated code // Inlining this functions somehow break the IntelliJ debugger, unclear why? internal fun realmEquals(obj: BaseRealmObject, other: Any?): Boolean { if (obj === other) return true @@ -1180,12 +1181,18 @@ internal object RealmObjectHelper { other as BaseRealmObject - if (obj.isValid() != other.isValid()) return false - if (obj.getIdentifierOrNull() != other.getIdentifierOrNull()) return false - return (obj.realmObjectReference?.owner?.owner?.configuration?.path == other.realmObjectReference?.owner?.owner?.configuration?.path) + if (other.isManaged()) { + if (obj.isValid() != other.isValid()) return false + if (obj.getIdentifierOrNull() != other.getIdentifierOrNull()) return false + return (obj.realmObjectReference?.owner?.owner?.configuration?.path == other.realmObjectReference?.owner?.owner?.configuration?.path) + } else { + // If one of the objects are unmanaged, they are only equal if identical, which + // should have been caught at the top of this function. + return false + } } - @Suppress("unused") // Called from generated code + @Suppress("unused", "MagicNumber") // Called from generated code // Inlining this functions somehow break the IntelliJ debugger, unclear why? internal fun realmHashCode(obj: BaseRealmObject): Int { // This code assumes no race conditions diff --git a/packages/plugin-compiler/src/main/kotlin/io/realm/kotlin/compiler/RealmModelSyntheticMethodsExtension.kt b/packages/plugin-compiler/src/main/kotlin/io/realm/kotlin/compiler/RealmModelSyntheticMethodsExtension.kt index 7146e68915..5342091838 100644 --- a/packages/plugin-compiler/src/main/kotlin/io/realm/kotlin/compiler/RealmModelSyntheticMethodsExtension.kt +++ b/packages/plugin-compiler/src/main/kotlin/io/realm/kotlin/compiler/RealmModelSyntheticMethodsExtension.kt @@ -35,6 +35,7 @@ import org.jetbrains.kotlin.types.SimpleType * Triggers generation of synthetic methods on Realm model classes, in particular * `toString()`, `equals()` and `hashCode()`. */ +@Suppress("ComplexCondition") class RealmModelSyntheticMethodsExtension : SyntheticResolveExtension { override fun generateSyntheticMethods( diff --git a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmListTests.kt b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmListTests.kt index 20f7aa8f2e..4e1bcf0b8f 100644 --- a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmListTests.kt +++ b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmListTests.kt @@ -282,6 +282,13 @@ class RealmListTests : EmbeddedObjectCollectionQueryTests { managedTesters[0].clearFailsIfClosed(getCloseableRealm()) } + @Test + fun remove() { + for (tester in managedTesters) { + tester.remove() + } + } + @Test fun removeAt() { for (tester in managedTesters) { @@ -729,6 +736,7 @@ internal interface ListApiTester : ErrorCatcher { fun addAllWithIndexFailsIfClosed(realm: Realm) fun clear() fun clearFailsIfClosed(realm: Realm) + fun remove() fun removeAt() fun removeAtFailsIfClosed(realm: Realm) fun set() @@ -809,7 +817,7 @@ internal class ListTypeSafetyManager( */ internal abstract class ManagedListTester( override val realm: Realm, - private val typeSafetyManager: ListTypeSafetyManager, + protected val typeSafetyManager: ListTypeSafetyManager, override val classifier: KClassifier ) : ListApiTester { @@ -1068,6 +1076,25 @@ internal abstract class ManagedListTester( } } + override fun remove() { + val dataSet = typeSafetyManager.dataSetToLoad + val assertions = { list: RealmList -> + assertTrue(list.isEmpty()) + } + + errorCatcher { + realm.writeBlocking { + val list = typeSafetyManager.createContainerAndGetCollection(this) + assertFalse(list.remove(dataSet[0])) + assertTrue(list.add(dataSet[0])) + assertTrue(list.remove(list.last())) + assertions(list) + } + } + + assertListAndCleanup { list -> assertions(list) } + } + override fun removeAt() { val dataSet = typeSafetyManager.dataSetToLoad val assertions = { list: RealmList -> @@ -1204,7 +1231,7 @@ internal abstract class ManagedListTester( } // Retrieves the list again but this time from Realm to check the getter is called correctly - private fun assertListAndCleanup(assertion: (RealmList) -> Unit) { + protected fun assertListAndCleanup(assertion: (RealmList) -> Unit) { realm.writeBlocking { val container = this.query() .first() @@ -1318,6 +1345,27 @@ internal class ByteArrayListTester( ) : ManagedListTester(realm, typeSafetyManager, ByteArray::class) { override fun assertElementsAreEqual(expected: ByteArray?, actual: ByteArray?) = assertContentEquals(expected, actual) + + // Removing elements using equals/hashcode will fail for byte arrays since they are + // are only equal if identical + override fun remove() { + val dataSet = typeSafetyManager.dataSetToLoad + val assertions = { list: RealmList -> + assertFalse(list.isEmpty()) + } + + errorCatcher { + realm.writeBlocking { + val list = typeSafetyManager.createContainerAndGetCollection(this) + assertFalse(list.remove(dataSet[0])) + assertTrue(list.add(dataSet[0])) + assertFalse(list.remove(list.last())) + assertions(list) + } + } + + assertListAndCleanup { list -> assertions(list) } + } } // ----------------------------------- diff --git a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmObjectTests.kt b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmObjectTests.kt index 5f4eb1487f..28cfef2dc1 100644 --- a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmObjectTests.kt +++ b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmObjectTests.kt @@ -145,7 +145,7 @@ class RealmObjectTests : RealmStateTest { val managedObj = realm.writeBlocking { copyToRealm(Parent()) } - val regex = Regex("io.realm.kotlin.entities.link.Parent\\{state=VALID, schemaName=Parent, objKey=[0-9]*, version=[0-9]*, realm=${realm.configuration.path}}") + val regex = Regex("io.realm.kotlin.entities.link.Parent\\{state=VALID, schemaName=Parent, objKey=[0-9]*, version=[0-9]*, realm=${realm.configuration.name}}") assertTrue(regex.matches(managedObj.toString()), managedObj.toString()) } @@ -155,7 +155,7 @@ class RealmObjectTests : RealmStateTest { p1.name = "Parent" p1.otherParent = p1 val managedObj = realm.writeBlocking { copyToRealm(p1) } - val regex = Regex("io.realm.kotlin.entities.link.Parent\\{state=VALID, schemaName=Parent, objKey=[0-9]*, version=[0-9]*, realm=${realm.configuration.path}}") + val regex = Regex("io.realm.kotlin.entities.link.Parent\\{state=VALID, schemaName=Parent, objKey=[0-9]*, version=[0-9]*, realm=${realm.configuration.name}}") assertTrue(regex.matches(managedObj.toString()), managedObj.toString()) } @@ -164,7 +164,7 @@ class RealmObjectTests : RealmStateTest { realm.writeBlocking { val managedObject = copyToRealm(Parent()) delete(managedObject) - val regex = Regex("io.realm.kotlin.entities.link.Parent\\{state=INVALID, schemaName=Parent, realm=${realm.configuration.path}, hashCode=[0-9]*\\}") + val regex = Regex("io.realm.kotlin.entities.link.Parent\\{state=INVALID, schemaName=Parent, realm=${realm.configuration.name}, hashCode=[0-9]*\\}") assertTrue(regex.matches(managedObject.toString()), managedObject.toString()) cancelWrite() } @@ -176,7 +176,7 @@ class RealmObjectTests : RealmStateTest { copyToRealm(Parent()) } realm.close() - val regex = Regex("io.realm.kotlin.entities.link.Parent\\{state=CLOSED, schemaName=Parent, realm=${realm.configuration.path}, hashCode=[0-9]*\\}") + val regex = Regex("io.realm.kotlin.entities.link.Parent\\{state=CLOSED, schemaName=Parent, realm=${realm.configuration.name}, hashCode=[0-9]*\\}") assertTrue(regex.matches(managedObject.toString()), managedObject.toString()) } @@ -195,7 +195,7 @@ class RealmObjectTests : RealmStateTest { } @Test - fun equals_managed() { + fun equals_hashCode_managed() { realm.writeBlocking { val p1 = copyToRealm(Parent().apply { this.name = "Jane" }) val p2 = copyToRealm(Parent()) @@ -212,7 +212,7 @@ class RealmObjectTests : RealmStateTest { } @Test - fun equals_unmanaged() { + fun equals_hashCode_unmanaged() { val p1 = Parent() val p2 = Parent() assertEquals(p1, p1) @@ -221,7 +221,7 @@ class RealmObjectTests : RealmStateTest { } @Test - fun equals_mixed() { + fun equals_hashCode_mixed() { val unmanagedObj = Parent() val managedObj = realm.writeBlocking { copyToRealm(Parent()) } assertNotEquals(unmanagedObj, managedObj) @@ -229,7 +229,7 @@ class RealmObjectTests : RealmStateTest { } @Test - fun equals_managed_cyclicData() { + fun equals_hashCode_managed_cyclicData() { realm.writeBlocking { val p1 = copyToRealm(Parent().apply { this.name = "Jane" }) p1.otherParent = p1 @@ -247,18 +247,20 @@ class RealmObjectTests : RealmStateTest { } @Test - fun equals_customMethod() { + fun equals_hashCode_customMethod() { // Only equals if age = 42 or same instance val obj1 = CustomMethods() val obj2 = CustomMethods() assertEquals(obj1, obj1) assertEquals(obj1.hashCode(), obj1.hashCode()) + assertEquals(42, obj1.hashCode()) assertNotEquals(obj1, obj2) val obj3 = CustomMethods().apply { age = 42 } val obj4 = CustomMethods().apply { age = 42 } assertEquals(obj3, obj3) assertEquals(obj3.hashCode(), obj4.hashCode()) + assertEquals(42, obj3.hashCode()) assertEquals(obj3, obj4) assertEquals(obj3.hashCode(), obj4.hashCode()) @@ -268,20 +270,22 @@ class RealmObjectTests : RealmStateTest { val obj2 = copyToRealm(CustomMethods()) assertEquals(obj1, obj1) assertEquals(obj1.hashCode(), obj1.hashCode()) + assertEquals(42, obj1.hashCode()) assertNotEquals(obj1, obj2) val obj3 = copyToRealm(CustomMethods().apply { age = 42 }) val obj4 = copyToRealm(CustomMethods().apply { age = 42 }) assertEquals(obj3, obj3) assertEquals(obj3.hashCode(), obj3.hashCode()) + assertEquals(42, obj1.hashCode()) assertEquals(obj3, obj4) assertEquals(obj3.hashCode(), obj4.hashCode()) } } @Test - fun equals_managed_invalid() { - realm.writeBlocking { + fun equals_hashCode_managed_invalid() { + realm.writeBlocking { val p1 = copyToRealm(Parent().apply { this.name = "Jane" }) val p2 = copyToRealm(Parent()) delete(p1) @@ -293,31 +297,6 @@ class RealmObjectTests : RealmStateTest { } } - @Test - fun hashCode_managed() { -// TODO() - } - - @Test - fun hashCode_managed_cyclicData() { -// TODO() - } - - @Test - fun hashCode_customMethod() { -// TODO() - } - - @Test - fun hashCode_managed_invalid() { -// TODO() - } - - @Test - fun hashCode_unmanaged() { -// TODO() - } - override fun isFrozen_throwsIfRealmIsClosed() { realm.close() assertFailsWith { diff --git a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmSetTests.kt b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmSetTests.kt index e1e60b4b19..437ff45ebc 100644 --- a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmSetTests.kt +++ b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmSetTests.kt @@ -777,26 +777,26 @@ internal abstract class ManagedSetTester( } override fun remove() { - // TODO https://github.com/realm/realm-kotlin/issues/1097 - // Ignore RealmObject: structural equality cannot be assessed for this type when removing - // elements from the set - if (classifier != RealmObject::class) { - val dataSet = typeSafetyManager.dataSetToLoad + val dataSet = typeSafetyManager.dataSetToLoad - errorCatcher { - realm.writeBlocking { - val set = typeSafetyManager.createContainerAndGetCollection(this) - set.add(dataSet[0]) - assertTrue(set.remove(dataSet[0])) - assertTrue(set.isEmpty()) + errorCatcher { + realm.writeBlocking { + val set = typeSafetyManager.createContainerAndGetCollection(this) + val element = if (classifier == RealmObject::class) { + copyToRealm(dataSet[0] as RealmObject) as T + } else { + dataSet[0] } - } - - assertContainerAndCleanup { container -> - val set = typeSafetyManager.getCollection(container) + set.add(element) + assertTrue(set.remove(element)) assertTrue(set.isEmpty()) } } + + assertContainerAndCleanup { container -> + val set = typeSafetyManager.getCollection(container) + assertTrue(set.isEmpty()) + } } override fun removeAll() { From ec3222cc78c8769f44af83f07e5cff98e0003c87 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Mon, 14 Aug 2023 07:54:28 +0200 Subject: [PATCH 4/7] Schema compiler test --- .../01_AFTER.ValidateIrBeforeLowering.ir | 57 +++++++++++++++---- 1 file changed, 45 insertions(+), 12 deletions(-) diff --git a/packages/plugin-compiler/src/test/resources/schema/expected/01_AFTER.ValidateIrBeforeLowering.ir b/packages/plugin-compiler/src/test/resources/schema/expected/01_AFTER.ValidateIrBeforeLowering.ir index 84abd61195..72f689feb3 100644 --- a/packages/plugin-compiler/src/test/resources/schema/expected/01_AFTER.ValidateIrBeforeLowering.ir +++ b/packages/plugin-compiler/src/test/resources/schema/expected/01_AFTER.ValidateIrBeforeLowering.ir @@ -118,15 +118,26 @@ MODULE_FRAGMENT name:
RETURN type=kotlin.Nothing from='public final fun (): io.realm.kotlin.schema.RealmClassKind declared in schema.input.A.Companion' GET_FIELD 'FIELD PROPERTY_BACKING_FIELD name:io_realm_kotlin_classKind type:io.realm.kotlin.schema.RealmClassKind visibility:private' type=io.realm.kotlin.schema.RealmClassKind origin=null receiver: GET_VAR ': schema.input.A.Companion declared in schema.input.A.Companion.' type=schema.input.A.Companion origin=null - FUN FAKE_OVERRIDE name:equals visibility:public modality:OPEN <> ($this:kotlin.Any, other:kotlin.Any?) returnType:kotlin.Boolean [fake_override,operator] + FUN name:equals visibility:public modality:OPEN <> ($this:schema.input.A, other:kotlin.Any?) returnType:kotlin.Boolean [operator] overridden: public open fun equals (other: kotlin.Any?): kotlin.Boolean [fake_override,operator] declared in io.realm.kotlin.types.RealmObject - $this: VALUE_PARAMETER name: type:kotlin.Any + $this: VALUE_PARAMETER name: type:schema.input.A VALUE_PARAMETER name:other index:0 type:kotlin.Any? - FUN FAKE_OVERRIDE name:hashCode visibility:public modality:OPEN <> ($this:kotlin.Any) returnType:kotlin.Int [fake_override] + BLOCK_BODY + RETURN type=kotlin.Nothing from='public open fun equals (other: kotlin.Any?): kotlin.Boolean [operator] declared in schema.input.A' + CALL 'internal final fun realmEquals (obj: io.realm.kotlin.types.BaseRealmObject, other: kotlin.Any?): kotlin.Boolean declared in io.realm.kotlin.internal.RealmObjectHelper' type=kotlin.Boolean origin=null + $this: GET_OBJECT 'CLASS IR_EXTERNAL_DECLARATION_STUB OBJECT name:RealmObjectHelper modality:FINAL visibility:internal superTypes:[kotlin.Any]' type=io.realm.kotlin.internal.RealmObjectHelper + obj: GET_VAR ': schema.input.A declared in schema.input.A.equals' type=schema.input.A origin=null + other: GET_VAR 'other: kotlin.Any? declared in schema.input.A.equals' type=kotlin.Any? origin=null + FUN name:hashCode visibility:public modality:OPEN <> ($this:schema.input.A) returnType:kotlin.Int overridden: public open fun hashCode (): kotlin.Int [fake_override] declared in io.realm.kotlin.types.RealmObject - $this: VALUE_PARAMETER name: type:kotlin.Any + $this: VALUE_PARAMETER name: type:schema.input.A + BLOCK_BODY + RETURN type=kotlin.Nothing from='public open fun hashCode (): kotlin.Int declared in schema.input.A' + CALL 'internal final fun realmHashCode (obj: io.realm.kotlin.types.BaseRealmObject): kotlin.Int declared in io.realm.kotlin.internal.RealmObjectHelper' type=kotlin.Int origin=null + $this: GET_OBJECT 'CLASS IR_EXTERNAL_DECLARATION_STUB OBJECT name:RealmObjectHelper modality:FINAL visibility:internal superTypes:[kotlin.Any]' type=io.realm.kotlin.internal.RealmObjectHelper + obj: GET_VAR ': schema.input.A declared in schema.input.A.hashCode' type=schema.input.A origin=null FUN name:toString visibility:public modality:OPEN <> ($this:schema.input.A) returnType:kotlin.String overridden: public open fun toString (): kotlin.String [fake_override] declared in io.realm.kotlin.types.RealmObject @@ -276,15 +287,26 @@ MODULE_FRAGMENT name:
RETURN type=kotlin.Nothing from='public final fun (): io.realm.kotlin.schema.RealmClassKind declared in schema.input.B.Companion' GET_FIELD 'FIELD PROPERTY_BACKING_FIELD name:io_realm_kotlin_classKind type:io.realm.kotlin.schema.RealmClassKind visibility:private' type=io.realm.kotlin.schema.RealmClassKind origin=null receiver: GET_VAR ': schema.input.B.Companion declared in schema.input.B.Companion.' type=schema.input.B.Companion origin=null - FUN FAKE_OVERRIDE name:equals visibility:public modality:OPEN <> ($this:kotlin.Any, other:kotlin.Any?) returnType:kotlin.Boolean [fake_override,operator] + FUN name:equals visibility:public modality:OPEN <> ($this:schema.input.B, other:kotlin.Any?) returnType:kotlin.Boolean [operator] overridden: public open fun equals (other: kotlin.Any?): kotlin.Boolean [fake_override,operator] declared in io.realm.kotlin.types.RealmObject - $this: VALUE_PARAMETER name: type:kotlin.Any + $this: VALUE_PARAMETER name: type:schema.input.B VALUE_PARAMETER name:other index:0 type:kotlin.Any? - FUN FAKE_OVERRIDE name:hashCode visibility:public modality:OPEN <> ($this:kotlin.Any) returnType:kotlin.Int [fake_override] + BLOCK_BODY + RETURN type=kotlin.Nothing from='public open fun equals (other: kotlin.Any?): kotlin.Boolean [operator] declared in schema.input.B' + CALL 'internal final fun realmEquals (obj: io.realm.kotlin.types.BaseRealmObject, other: kotlin.Any?): kotlin.Boolean declared in io.realm.kotlin.internal.RealmObjectHelper' type=kotlin.Boolean origin=null + $this: GET_OBJECT 'CLASS IR_EXTERNAL_DECLARATION_STUB OBJECT name:RealmObjectHelper modality:FINAL visibility:internal superTypes:[kotlin.Any]' type=io.realm.kotlin.internal.RealmObjectHelper + obj: GET_VAR ': schema.input.B declared in schema.input.B.equals' type=schema.input.B origin=null + other: GET_VAR 'other: kotlin.Any? declared in schema.input.B.equals' type=kotlin.Any? origin=null + FUN name:hashCode visibility:public modality:OPEN <> ($this:schema.input.B) returnType:kotlin.Int overridden: public open fun hashCode (): kotlin.Int [fake_override] declared in io.realm.kotlin.types.RealmObject - $this: VALUE_PARAMETER name: type:kotlin.Any + $this: VALUE_PARAMETER name: type:schema.input.B + BLOCK_BODY + RETURN type=kotlin.Nothing from='public open fun hashCode (): kotlin.Int declared in schema.input.B' + CALL 'internal final fun realmHashCode (obj: io.realm.kotlin.types.BaseRealmObject): kotlin.Int declared in io.realm.kotlin.internal.RealmObjectHelper' type=kotlin.Int origin=null + $this: GET_OBJECT 'CLASS IR_EXTERNAL_DECLARATION_STUB OBJECT name:RealmObjectHelper modality:FINAL visibility:internal superTypes:[kotlin.Any]' type=io.realm.kotlin.internal.RealmObjectHelper + obj: GET_VAR ': schema.input.B declared in schema.input.B.hashCode' type=schema.input.B origin=null FUN name:toString visibility:public modality:OPEN <> ($this:schema.input.B) returnType:kotlin.String overridden: public open fun toString (): kotlin.String [fake_override] declared in io.realm.kotlin.types.RealmObject @@ -434,15 +456,26 @@ MODULE_FRAGMENT name:
RETURN type=kotlin.Nothing from='public final fun (): io.realm.kotlin.schema.RealmClassKind declared in schema.input.C.Companion' GET_FIELD 'FIELD PROPERTY_BACKING_FIELD name:io_realm_kotlin_classKind type:io.realm.kotlin.schema.RealmClassKind visibility:private' type=io.realm.kotlin.schema.RealmClassKind origin=null receiver: GET_VAR ': schema.input.C.Companion declared in schema.input.C.Companion.' type=schema.input.C.Companion origin=null - FUN FAKE_OVERRIDE name:equals visibility:public modality:OPEN <> ($this:kotlin.Any, other:kotlin.Any?) returnType:kotlin.Boolean [fake_override,operator] + FUN name:equals visibility:public modality:OPEN <> ($this:schema.input.C, other:kotlin.Any?) returnType:kotlin.Boolean [operator] overridden: public open fun equals (other: kotlin.Any?): kotlin.Boolean [fake_override,operator] declared in io.realm.kotlin.types.RealmObject - $this: VALUE_PARAMETER name: type:kotlin.Any + $this: VALUE_PARAMETER name: type:schema.input.C VALUE_PARAMETER name:other index:0 type:kotlin.Any? - FUN FAKE_OVERRIDE name:hashCode visibility:public modality:OPEN <> ($this:kotlin.Any) returnType:kotlin.Int [fake_override] + BLOCK_BODY + RETURN type=kotlin.Nothing from='public open fun equals (other: kotlin.Any?): kotlin.Boolean [operator] declared in schema.input.C' + CALL 'internal final fun realmEquals (obj: io.realm.kotlin.types.BaseRealmObject, other: kotlin.Any?): kotlin.Boolean declared in io.realm.kotlin.internal.RealmObjectHelper' type=kotlin.Boolean origin=null + $this: GET_OBJECT 'CLASS IR_EXTERNAL_DECLARATION_STUB OBJECT name:RealmObjectHelper modality:FINAL visibility:internal superTypes:[kotlin.Any]' type=io.realm.kotlin.internal.RealmObjectHelper + obj: GET_VAR ': schema.input.C declared in schema.input.C.equals' type=schema.input.C origin=null + other: GET_VAR 'other: kotlin.Any? declared in schema.input.C.equals' type=kotlin.Any? origin=null + FUN name:hashCode visibility:public modality:OPEN <> ($this:schema.input.C) returnType:kotlin.Int overridden: public open fun hashCode (): kotlin.Int [fake_override] declared in io.realm.kotlin.types.RealmObject - $this: VALUE_PARAMETER name: type:kotlin.Any + $this: VALUE_PARAMETER name: type:schema.input.C + BLOCK_BODY + RETURN type=kotlin.Nothing from='public open fun hashCode (): kotlin.Int declared in schema.input.C' + CALL 'internal final fun realmHashCode (obj: io.realm.kotlin.types.BaseRealmObject): kotlin.Int declared in io.realm.kotlin.internal.RealmObjectHelper' type=kotlin.Int origin=null + $this: GET_OBJECT 'CLASS IR_EXTERNAL_DECLARATION_STUB OBJECT name:RealmObjectHelper modality:FINAL visibility:internal superTypes:[kotlin.Any]' type=io.realm.kotlin.internal.RealmObjectHelper + obj: GET_VAR ': schema.input.C declared in schema.input.C.hashCode' type=schema.input.C origin=null FUN name:toString visibility:public modality:OPEN <> ($this:schema.input.C) returnType:kotlin.String overridden: public open fun toString (): kotlin.String [fake_override] declared in io.realm.kotlin.types.RealmObject From a2fed033832eb06d404aa0b7a99ea474ed525e4a Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Mon, 14 Aug 2023 08:40:22 +0200 Subject: [PATCH 5/7] Fix tests and regexes --- .../io/realm/kotlin/entities/link/Parent.kt | 1 - .../kotlin/test/common/RealmObjectTests.kt | 35 ++++++++++++------- 2 files changed, 23 insertions(+), 13 deletions(-) diff --git a/packages/test-base/src/commonMain/kotlin/io/realm/kotlin/entities/link/Parent.kt b/packages/test-base/src/commonMain/kotlin/io/realm/kotlin/entities/link/Parent.kt index f77d627760..45675d7a3f 100644 --- a/packages/test-base/src/commonMain/kotlin/io/realm/kotlin/entities/link/Parent.kt +++ b/packages/test-base/src/commonMain/kotlin/io/realm/kotlin/entities/link/Parent.kt @@ -21,5 +21,4 @@ import io.realm.kotlin.types.RealmObject class Parent : RealmObject { var name: String = "N.N." var child: Child? = null - var otherParent: Parent? = null } diff --git a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmObjectTests.kt b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmObjectTests.kt index 28cfef2dc1..3031cce363 100644 --- a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmObjectTests.kt +++ b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmObjectTests.kt @@ -18,6 +18,7 @@ package io.realm.kotlin.test.common import io.realm.kotlin.Realm import io.realm.kotlin.RealmConfiguration import io.realm.kotlin.VersionId +import io.realm.kotlin.entities.SampleWithPrimaryKey import io.realm.kotlin.entities.link.Child import io.realm.kotlin.entities.link.Parent import io.realm.kotlin.ext.isFrozen @@ -75,7 +76,7 @@ class RealmObjectTests : RealmStateTest { @BeforeTest fun setup() { tmpDir = PlatformUtils.createTempDir() - val configuration = RealmConfiguration.Builder(schema = setOf(Parent::class, Child::class, CustomMethods::class)) + val configuration = RealmConfiguration.Builder(schema = setOf(Parent::class, Child::class, SampleWithPrimaryKey::class, CustomMethods::class)) .directory(tmpDir) .build() realm = Realm.open(configuration) @@ -151,11 +152,11 @@ class RealmObjectTests : RealmStateTest { @Test fun toString_managed_cyclicData() { - val p1 = Parent() - p1.name = "Parent" - p1.otherParent = p1 + val p1 = SampleWithPrimaryKey() + p1.stringField = "Parent" + p1.nullableObject = p1 val managedObj = realm.writeBlocking { copyToRealm(p1) } - val regex = Regex("io.realm.kotlin.entities.link.Parent\\{state=VALID, schemaName=Parent, objKey=[0-9]*, version=[0-9]*, realm=${realm.configuration.name}}") + val regex = Regex("io.realm.kotlin.entities.SampleWithPrimaryKey\\{state=VALID, schemaName=SampleWithPrimaryKey, objKey=[0-9]*, version=[0-9]*, realm=${realm.configuration.name}\\}") assertTrue(regex.matches(managedObj.toString()), managedObj.toString()) } @@ -164,7 +165,7 @@ class RealmObjectTests : RealmStateTest { realm.writeBlocking { val managedObject = copyToRealm(Parent()) delete(managedObject) - val regex = Regex("io.realm.kotlin.entities.link.Parent\\{state=INVALID, schemaName=Parent, realm=${realm.configuration.name}, hashCode=[0-9]*\\}") + val regex = Regex("io.realm.kotlin.entities.link.Parent\\{state=INVALID, schemaName=Parent, realm=${realm.configuration.name}, hashCode=[-0-9]*\\}") assertTrue(regex.matches(managedObject.toString()), managedObject.toString()) cancelWrite() } @@ -176,7 +177,7 @@ class RealmObjectTests : RealmStateTest { copyToRealm(Parent()) } realm.close() - val regex = Regex("io.realm.kotlin.entities.link.Parent\\{state=CLOSED, schemaName=Parent, realm=${realm.configuration.name}, hashCode=[0-9]*\\}") + val regex = Regex("io.realm.kotlin.entities.link.Parent\\{state=CLOSED, schemaName=Parent, realm=${realm.configuration.name}, hashCode=[-0-9]*\\}") assertTrue(regex.matches(managedObject.toString()), managedObject.toString()) } @@ -190,7 +191,7 @@ class RealmObjectTests : RealmStateTest { @Test fun toString_unmanaged() { val unmanagedObject = Parent() - val regex = Regex("io.realm.kotlin.entities.link.Parent\\{state=UNMANAGED, schemaName=Parent, hashCode=[0-9]*\\}") + val regex = Regex("io.realm.kotlin.entities.link.Parent\\{state=UNMANAGED, schemaName=Parent, hashCode=[-0-9]*\\}") assertTrue(regex.matches(unmanagedObject.toString()), unmanagedObject.toString()) } @@ -231,10 +232,20 @@ class RealmObjectTests : RealmStateTest { @Test fun equals_hashCode_managed_cyclicData() { realm.writeBlocking { - val p1 = copyToRealm(Parent().apply { this.name = "Jane" }) - p1.otherParent = p1 - val p2 = copyToRealm(Parent()) - val p3 = query(Parent::class, "name = 'Jane'").first().find()!! + val p1 = copyToRealm( + SampleWithPrimaryKey().apply { + primaryKey = 1 + stringField = "Jane" + } + ) + p1.nullableObject = p1 + val p2 = copyToRealm( + SampleWithPrimaryKey().apply { + primaryKey = 2 + stringField = "John" + } + ) + val p3 = query(SampleWithPrimaryKey::class, "stringField = 'Jane'").first().find()!! assertEquals(p1, p1) assertEquals(p1, p3) assertEquals(p1.hashCode(), p1.hashCode()) From 9296d160176787c9f3e87ab8af53fd5c1ce0fea0 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Mon, 14 Aug 2023 08:58:33 +0200 Subject: [PATCH 6/7] Fix syntax --- .../kotlin/io/realm/kotlin/test/common/RealmObjectTests.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmObjectTests.kt b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmObjectTests.kt index 3031cce363..30f8f5b6fa 100644 --- a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmObjectTests.kt +++ b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmObjectTests.kt @@ -146,7 +146,7 @@ class RealmObjectTests : RealmStateTest { val managedObj = realm.writeBlocking { copyToRealm(Parent()) } - val regex = Regex("io.realm.kotlin.entities.link.Parent\\{state=VALID, schemaName=Parent, objKey=[0-9]*, version=[0-9]*, realm=${realm.configuration.name}}") + val regex = Regex("io.realm.kotlin.entities.link.Parent\\{state=VALID, schemaName=Parent, objKey=[0-9]*, version=[0-9]*, realm=${realm.configuration.name}\\}") assertTrue(regex.matches(managedObj.toString()), managedObj.toString()) } From 2844424ef33a94ca9f9a01906315513a5b948734 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 15 Aug 2023 15:57:32 +0200 Subject: [PATCH 7/7] PR feedback --- CHANGELOG.md | 20 ++++++++++++++++++- .../kotlin/internal/RealmObjectHelper.kt | 13 ++++++------ .../realm/kotlin/internal/RealmObjectUtil.kt | 7 +++---- .../io/realm/kotlin/internal/RealmUtils.kt | 9 +++++++-- .../kotlin/test/common/RealmObjectTests.kt | 2 ++ 5 files changed, 37 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a84b3739f7..5c9f8212d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,25 @@ ## 1.11.0-SNAPSHOT (YYYY-MM-DD) ### Breaking Changes -* None. +* `BaseRealmObject.equals()` has changed from being identity-based only (===) to instead return `true` if two objects come from the same Realm version. This e.g means that reading the same object property twice will now be identical. Note, two Realm objects, even with identical values will not be considered equal if they belong to different versions. + +``` +val childA: Child = realm.query().first().find()!! +val childB: Child = realm.query().first().find()!! + +// This behavior is the same both before 1.11.0 and before +childA === childB // false + +// This will return true in 1.11.0 and onwards. Before it will return false +childA == childB + +realm.writeBlocking { /* Do a write */ } +val childC = realm.query().first().find()!! + +// This will return false because childA belong to version 1, while childC belong to version 2. +// Override equals/hashCode if value semantics are wanted. +childA == childC +``` ### Enhancements * Realm model classes now generate custom `toString`, `equals` and `hashCode` implementations. This makes it possible to compare by object reference across multiple collections. Note that two objects at different versions will not be considered equal, even diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmObjectHelper.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmObjectHelper.kt index 6936b9e3f0..8351ee4be2 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmObjectHelper.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmObjectHelper.kt @@ -1158,9 +1158,9 @@ internal object RealmObjectHelper { val fqName = obj::class.qualifiedName return obj.realmObjectReference?.let { if (obj.isValid()) { - val id: Triple = obj.getIdentifier() - val objKey = id.second.key - val version = id.third.version + val id: RealmObjectIdentifier = obj.getIdentifier() + val objKey = id.objectKey.key + val version = id.versionId.version "$fqName{state=VALID, schemaName=$schemaName, objKey=$objKey, version=$version, realm=${it.owner.owner.configuration.name}}" } else { val state = if (it.owner.isClosed()) { @@ -1183,8 +1183,7 @@ internal object RealmObjectHelper { if (other.isManaged()) { if (obj.isValid() != other.isValid()) return false - if (obj.getIdentifierOrNull() != other.getIdentifierOrNull()) return false - return (obj.realmObjectReference?.owner?.owner?.configuration?.path == other.realmObjectReference?.owner?.owner?.configuration?.path) + return obj.getIdentifierOrNull() == other.getIdentifierOrNull() } else { // If one of the objects are unmanaged, they are only equal if identical, which // should have been caught at the top of this function. @@ -1198,8 +1197,8 @@ internal object RealmObjectHelper { // This code assumes no race conditions return obj.realmObjectReference?.let { val isValid: Boolean = obj.isValid() - val identifier: Triple = if (it.isClosed()) { - Triple(ClassKey(-1), ObjectKey(-1), VersionId(0)) + val identifier: RealmObjectIdentifier = if (it.isClosed()) { + RealmObjectIdentifier(ClassKey(-1), ObjectKey(-1), VersionId(0), "") } else { obj.getIdentifier() } diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmObjectUtil.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmObjectUtil.kt index 5843f3e5e2..06c796f828 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmObjectUtil.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmObjectUtil.kt @@ -142,15 +142,14 @@ internal fun BaseRealmObject.getIdentifier(): RealmObjectIdentifier { val classKey: ClassKey = metadata.classKey val objKey: ObjectKey = RealmInterop.realm_object_get_key(objectPointer) val version: VersionId = version() - return Triple(classKey, objKey, version) + val path: String = owner.owner.configuration.path + return RealmObjectIdentifier(classKey, objKey, version, path) } ?: throw IllegalStateException("Identifier can only be calculated for managed objects.") } public fun BaseRealmObject.getIdentifierOrNull(): RealmObjectIdentifier? { - return if (realmObjectReference != null) { + return runIfManaged { getIdentifier() - } else { - null } } diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmUtils.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmUtils.kt index 9ba86380a5..702dd178fa 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmUtils.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmUtils.kt @@ -48,10 +48,15 @@ import kotlin.reflect.KProperty1 // `equals` method, which in general just is the memory address of the object. internal typealias UnmanagedToManagedObjectCache = MutableMap // Map -// For managed realm objects we use `` as a unique identifier +// For managed realm objects we use `` as a unique identifier // We are using a hash on the Kotlin side so we can use a HashMap for O(1) lookup rather than // having to do O(n) filter with a JNI call for `realm_equals` for each element. -internal typealias RealmObjectIdentifier = Triple +public data class RealmObjectIdentifier( + val classKey: ClassKey, + val objectKey: ObjectKey, + val versionId: VersionId, + val path: String +) internal typealias ManagedToUnmanagedObjectCache = MutableMap /** diff --git a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmObjectTests.kt b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmObjectTests.kt index 30f8f5b6fa..09cf50fec8 100644 --- a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmObjectTests.kt +++ b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmObjectTests.kt @@ -227,6 +227,8 @@ class RealmObjectTests : RealmStateTest { val managedObj = realm.writeBlocking { copyToRealm(Parent()) } assertNotEquals(unmanagedObj, managedObj) assertNotEquals(managedObj, unmanagedObj) + // When objects are not equal, no guarantees are given on the behavior of hashCode() + // thus nothing can be asserted here. } @Test