All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.realm.kotlin.compiler.AccessorModifierIrGeneration.kt Maven / Gradle / Ivy

/*
 * 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.
 */

@file:OptIn(UnsafeDuringIrConstructionAPI::class)

package io.realm.kotlin.compiler

import io.realm.kotlin.compiler.ClassIds.ASYMMETRIC_OBJECT_INTERFACE
import io.realm.kotlin.compiler.ClassIds.EMBEDDED_OBJECT_INTERFACE
import io.realm.kotlin.compiler.ClassIds.IGNORE_ANNOTATION
import io.realm.kotlin.compiler.ClassIds.KBSON_DECIMAL128
import io.realm.kotlin.compiler.ClassIds.KBSON_OBJECT_ID
import io.realm.kotlin.compiler.ClassIds.REALM_ANY
import io.realm.kotlin.compiler.ClassIds.REALM_BACKLINKS
import io.realm.kotlin.compiler.ClassIds.REALM_DICTIONARY
import io.realm.kotlin.compiler.ClassIds.REALM_EMBEDDED_BACKLINKS
import io.realm.kotlin.compiler.ClassIds.REALM_INSTANT
import io.realm.kotlin.compiler.ClassIds.REALM_LIST
import io.realm.kotlin.compiler.ClassIds.REALM_MUTABLE_INTEGER
import io.realm.kotlin.compiler.ClassIds.REALM_OBJECT_HELPER
import io.realm.kotlin.compiler.ClassIds.REALM_OBJECT_INTERFACE
import io.realm.kotlin.compiler.ClassIds.REALM_SET
import io.realm.kotlin.compiler.ClassIds.REALM_UUID
import io.realm.kotlin.compiler.ClassIds.TRANSIENT_ANNOTATION
import io.realm.kotlin.compiler.Names.OBJECT_REFERENCE
import io.realm.kotlin.compiler.Names.REALM_ACCESSOR_HELPER_GET_BOOLEAN
import io.realm.kotlin.compiler.Names.REALM_ACCESSOR_HELPER_GET_BYTE_ARRAY
import io.realm.kotlin.compiler.Names.REALM_ACCESSOR_HELPER_GET_DECIMAL128
import io.realm.kotlin.compiler.Names.REALM_ACCESSOR_HELPER_GET_DOUBLE
import io.realm.kotlin.compiler.Names.REALM_ACCESSOR_HELPER_GET_FLOAT
import io.realm.kotlin.compiler.Names.REALM_ACCESSOR_HELPER_GET_INSTANT
import io.realm.kotlin.compiler.Names.REALM_ACCESSOR_HELPER_GET_LONG
import io.realm.kotlin.compiler.Names.REALM_ACCESSOR_HELPER_GET_OBJECT_ID
import io.realm.kotlin.compiler.Names.REALM_ACCESSOR_HELPER_GET_REALM_ANY
import io.realm.kotlin.compiler.Names.REALM_ACCESSOR_HELPER_GET_STRING
import io.realm.kotlin.compiler.Names.REALM_ACCESSOR_HELPER_GET_UUID
import io.realm.kotlin.compiler.Names.REALM_ACCESSOR_HELPER_SET_VALUE
import io.realm.kotlin.compiler.Names.REALM_OBJECT_HELPER_GET_DICTIONARY
import io.realm.kotlin.compiler.Names.REALM_OBJECT_HELPER_GET_LIST
import io.realm.kotlin.compiler.Names.REALM_OBJECT_HELPER_GET_MUTABLE_INT
import io.realm.kotlin.compiler.Names.REALM_OBJECT_HELPER_GET_OBJECT
import io.realm.kotlin.compiler.Names.REALM_OBJECT_HELPER_GET_SET
import io.realm.kotlin.compiler.Names.REALM_OBJECT_HELPER_SET_DICTIONARY
import io.realm.kotlin.compiler.Names.REALM_OBJECT_HELPER_SET_EMBEDDED_REALM_OBJECT
import io.realm.kotlin.compiler.Names.REALM_OBJECT_HELPER_SET_LIST
import io.realm.kotlin.compiler.Names.REALM_OBJECT_HELPER_SET_OBJECT
import io.realm.kotlin.compiler.Names.REALM_OBJECT_HELPER_SET_SET
import io.realm.kotlin.compiler.Names.REALM_SYNTHETIC_PROPERTY_PREFIX
import io.realm.kotlin.compiler.fir.RealmPluginGeneratorKey
import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext
import org.jetbrains.kotlin.ir.IrStatement
import org.jetbrains.kotlin.ir.builders.IrBlockBuilder
import org.jetbrains.kotlin.ir.builders.Scope
import org.jetbrains.kotlin.ir.builders.irBlock
import org.jetbrains.kotlin.ir.builders.irBlockBody
import org.jetbrains.kotlin.ir.builders.irCall
import org.jetbrains.kotlin.ir.builders.irGet
import org.jetbrains.kotlin.ir.builders.irGetField
import org.jetbrains.kotlin.ir.builders.irGetObject
import org.jetbrains.kotlin.ir.builders.irIfNull
import org.jetbrains.kotlin.ir.builders.irReturn
import org.jetbrains.kotlin.ir.builders.irSetField
import org.jetbrains.kotlin.ir.builders.irString
import org.jetbrains.kotlin.ir.builders.irTemporary
import org.jetbrains.kotlin.ir.declarations.IrClass
import org.jetbrains.kotlin.ir.declarations.IrDeclarationOrigin
import org.jetbrains.kotlin.ir.declarations.IrProperty
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
import org.jetbrains.kotlin.ir.declarations.IrValueParameter
import org.jetbrains.kotlin.ir.descriptors.toIrBasedDescriptor
import org.jetbrains.kotlin.ir.descriptors.toIrBasedKotlinType
import org.jetbrains.kotlin.ir.expressions.IrCall
import org.jetbrains.kotlin.ir.expressions.IrDeclarationReference
import org.jetbrains.kotlin.ir.symbols.UnsafeDuringIrConstructionAPI
import org.jetbrains.kotlin.ir.types.IrSimpleType
import org.jetbrains.kotlin.ir.types.IrType
import org.jetbrains.kotlin.ir.types.IrTypeArgument
import org.jetbrains.kotlin.ir.types.impl.IrAbstractSimpleType
import org.jetbrains.kotlin.ir.types.isBoolean
import org.jetbrains.kotlin.ir.types.isByte
import org.jetbrains.kotlin.ir.types.isByteArray
import org.jetbrains.kotlin.ir.types.isChar
import org.jetbrains.kotlin.ir.types.isDouble
import org.jetbrains.kotlin.ir.types.isFloat
import org.jetbrains.kotlin.ir.types.isInt
import org.jetbrains.kotlin.ir.types.isLong
import org.jetbrains.kotlin.ir.types.isNullable
import org.jetbrains.kotlin.ir.types.isShort
import org.jetbrains.kotlin.ir.types.isString
import org.jetbrains.kotlin.ir.types.isSubtypeOfClass
import org.jetbrains.kotlin.ir.types.makeNotNull
import org.jetbrains.kotlin.ir.util.classId
import org.jetbrains.kotlin.ir.util.defaultType
import org.jetbrains.kotlin.ir.util.parentAsClass
import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid
import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
import org.jetbrains.kotlin.name.CallableId
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.resolve.descriptorUtil.classId
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.StarProjectionImpl
import org.jetbrains.kotlin.types.isNullable
import org.jetbrains.kotlin.types.typeUtil.supertypes
import kotlin.collections.set

/**
 * Modifies the IR tree to transform getter/setter to call the C-Interop layer to retrieve read the managed values from the Realm
 * It also collect the schema information while processing the class properties.
 */
class AccessorModifierIrGeneration(private val pluginContext: IrPluginContext) {

    private val realmObjectHelper: IrClass = pluginContext.lookupClassOrThrow(REALM_OBJECT_HELPER)
    private val realmListClass: IrClass = pluginContext.lookupClassOrThrow(REALM_LIST)
    private val realmSetClass: IrClass = pluginContext.lookupClassOrThrow(REALM_SET)
    private val realmDictionaryClass: IrClass = pluginContext.lookupClassOrThrow(REALM_DICTIONARY)
    private val realmInstantClass: IrClass = pluginContext.lookupClassOrThrow(REALM_INSTANT)
    private val realmBacklinksClass: IrClass = pluginContext.lookupClassOrThrow(REALM_BACKLINKS)
    private val realmEmbeddedBacklinksClass: IrClass = pluginContext.lookupClassOrThrow(REALM_EMBEDDED_BACKLINKS)
    private val realmObjectInterface = pluginContext.lookupClassOrThrow(REALM_OBJECT_INTERFACE).symbol
    private val embeddedRealmObjectInterface = pluginContext.lookupClassOrThrow(EMBEDDED_OBJECT_INTERFACE).symbol

    private val objectIdClass: IrClass = pluginContext.lookupClassOrThrow(KBSON_OBJECT_ID)
    private val decimal128Class: IrClass = pluginContext.lookupClassOrThrow(KBSON_DECIMAL128)
    private val realmUUIDClass: IrClass = pluginContext.lookupClassOrThrow(REALM_UUID)
    private val mutableRealmIntegerClass: IrClass = pluginContext.lookupClassOrThrow(REALM_MUTABLE_INTEGER)
    private val realmAnyClass: IrClass = pluginContext.lookupClassOrThrow(REALM_ANY)

    // Primitive (Core) type getters
    private val getString: IrSimpleFunction =
        realmObjectHelper.lookupFunction(REALM_ACCESSOR_HELPER_GET_STRING)
    private val getLong: IrSimpleFunction =
        realmObjectHelper.lookupFunction(REALM_ACCESSOR_HELPER_GET_LONG)
    private val getBoolean: IrSimpleFunction =
        realmObjectHelper.lookupFunction(REALM_ACCESSOR_HELPER_GET_BOOLEAN)
    private val getFloat: IrSimpleFunction =
        realmObjectHelper.lookupFunction(REALM_ACCESSOR_HELPER_GET_FLOAT)
    private val getDouble: IrSimpleFunction =
        realmObjectHelper.lookupFunction(REALM_ACCESSOR_HELPER_GET_DOUBLE)
    private val getDecimal128: IrSimpleFunction =
        realmObjectHelper.lookupFunction(REALM_ACCESSOR_HELPER_GET_DECIMAL128)
    private val getInstant: IrSimpleFunction =
        realmObjectHelper.lookupFunction(REALM_ACCESSOR_HELPER_GET_INSTANT)
    private val getObjectId: IrSimpleFunction =
        realmObjectHelper.lookupFunction(REALM_ACCESSOR_HELPER_GET_OBJECT_ID)
    private val getUUID: IrSimpleFunction =
        realmObjectHelper.lookupFunction(REALM_ACCESSOR_HELPER_GET_UUID)
    private val getByteArray: IrSimpleFunction =
        realmObjectHelper.lookupFunction(REALM_ACCESSOR_HELPER_GET_BYTE_ARRAY)
    private val getMutableInt: IrSimpleFunction =
        realmObjectHelper.lookupFunction(REALM_OBJECT_HELPER_GET_MUTABLE_INT)
    private val getRealmAny: IrSimpleFunction =
        realmObjectHelper.lookupFunction(REALM_ACCESSOR_HELPER_GET_REALM_ANY)
    private val getObject: IrSimpleFunction =
        realmObjectHelper.lookupFunction(REALM_OBJECT_HELPER_GET_OBJECT)

    // Primitive (Core) type setters
    private val setValue: IrSimpleFunction =
        realmObjectHelper.lookupFunction(REALM_ACCESSOR_HELPER_SET_VALUE)
    private val setObject: IrSimpleFunction =
        realmObjectHelper.lookupFunction(REALM_OBJECT_HELPER_SET_OBJECT)
    private val setEmbeddedRealmObject: IrSimpleFunction =
        realmObjectHelper.lookupFunction(REALM_OBJECT_HELPER_SET_EMBEDDED_REALM_OBJECT)

    // Getters and setters for collections
    private val getList: IrSimpleFunction =
        realmObjectHelper.lookupFunction(REALM_OBJECT_HELPER_GET_LIST)
    private val setList: IrSimpleFunction =
        realmObjectHelper.lookupFunction(REALM_OBJECT_HELPER_SET_LIST)
    private val getSet: IrSimpleFunction =
        realmObjectHelper.lookupFunction(REALM_OBJECT_HELPER_GET_SET)
    private val setSet: IrSimpleFunction =
        realmObjectHelper.lookupFunction(REALM_OBJECT_HELPER_SET_SET)
    private val getDictionary: IrSimpleFunction =
        realmObjectHelper.lookupFunction(REALM_OBJECT_HELPER_GET_DICTIONARY)
    private val setDictionary: IrSimpleFunction =
        realmObjectHelper.lookupFunction(REALM_OBJECT_HELPER_SET_DICTIONARY)

    // Top level SDK->Core converters
    private val byteToLong: IrSimpleFunction =
        pluginContext.referenceFunctions(CallableId(FqName("io.realm.kotlin.internal"), Name.identifier("byteToLong"))).first().owner
    private val charToLong: IrSimpleFunction =
        pluginContext.referenceFunctions(CallableId(FqName("io.realm.kotlin.internal"), Name.identifier("charToLong"))).first().owner
    private val shortToLong: IrSimpleFunction =
        pluginContext.referenceFunctions(CallableId(FqName("io.realm.kotlin.internal"), Name.identifier("shortToLong"))).first().owner
    private val intToLong: IrSimpleFunction =
        pluginContext.referenceFunctions(CallableId(FqName("io.realm.kotlin.internal"), Name.identifier("intToLong"))).first().owner

    // Top level Core->SDK converters
    private val longToByte: IrSimpleFunction =
        pluginContext.referenceFunctions(CallableId(FqName("io.realm.kotlin.internal"), Name.identifier("longToByte"))).first().owner
    private val longToChar: IrSimpleFunction =
        pluginContext.referenceFunctions(CallableId(FqName("io.realm.kotlin.internal"), Name.identifier("longToChar"))).first().owner
    private val longToShort: IrSimpleFunction =
        pluginContext.referenceFunctions(CallableId(FqName("io.realm.kotlin.internal"), Name.identifier("longToShort"))).first().owner
    private val longToInt: IrSimpleFunction =
        pluginContext.referenceFunctions(CallableId(FqName("io.realm.kotlin.internal"), Name.identifier("longToInt"))).first().owner

    private lateinit var objectReferenceProperty: IrProperty
    private lateinit var objectReferenceType: IrType

    fun modifyPropertiesAndCollectSchema(irClass: IrClass) {
        logDebug("Processing class ${irClass.name}")
        val fields = SchemaCollector.properties
            .getOrPut(irClass) {
                mutableMapOf()
            }

        objectReferenceProperty = irClass.lookupProperty(OBJECT_REFERENCE)
        objectReferenceType = objectReferenceProperty.backingField!!.type

        // Attempt to find the interface for asymmetric objects.
        // The class will normally only be on the classpath for library-sync builds, not
        // library-base builds.
        val asymmetricRealmObjectInterface: IrClass? = pluginContext.referenceClass(ASYMMETRIC_OBJECT_INTERFACE)?.owner

        irClass.transformChildrenVoid(object : IrElementTransformerVoid() {
            @Suppress("LongMethod")
            override fun visitProperty(declaration: IrProperty): IrStatement {
                val name = declaration.name.asString()

                // Don't redefine accessors for internal synthetic properties or process declarations of subclasses
                @Suppress("ComplexCondition")
                if (declaration.backingField == null ||
                    // If the getter's dispatch receiver is null we cannot generate our accessors
                    // so skip processing those (See https://github.com/realm/realm-kotlin/issues/1296)
                    declaration.getter?.dispatchReceiverParameter == null ||
                    name.startsWith(REALM_SYNTHETIC_PROPERTY_PREFIX) ||
                    declaration.parentAsClass != irClass
                ) {
                    return declaration
                }

                val propertyTypeRaw = declaration.backingField!!.type
                val propertyType = propertyTypeRaw.makeNotNull()
                val nullable = propertyTypeRaw.isNullable()
                val excludeProperty =
                    declaration.hasAnnotation(IGNORE_ANNOTATION) ||
                        declaration.hasAnnotation(TRANSIENT_ANNOTATION) ||
                        declaration.backingField!!.hasAnnotation(IGNORE_ANNOTATION) ||
                        declaration.backingField!!.hasAnnotation(TRANSIENT_ANNOTATION)

                // Check for property modifiers:
                // - Persisted properties must be marked `var`.
                // - `lateinit` is not allowed.
                // - Backlinks must be marked `val`. The compiler will enforce wrong use of `var`.
                // - `const` is not allowed. The compiler will enforce wrong use of `const` inside classes.
                if (!excludeProperty &&
                    !propertyType.isLinkingObject() &&
                    !propertyType.isEmbeddedLinkingObject()
                ) {
                    if (declaration.isLateinit) {
                        logError("Persisted properties must not be marked with `lateinit`.", declaration.locationOf())
                    }
                    if (!declaration.isVar) {
                        logError("Persisted properties must be marked with `var`. `val` is not supported.", declaration.locationOf())
                    }
                }

                when {
                    excludeProperty -> {
                        logDebug("Property named ${declaration.name} ignored")
                    }
                    propertyType.isMutableRealmInteger() -> {
                        logDebug("MutableRealmInt property named ${declaration.name} is ${if (nullable) "" else "not "}nullable")
                        val schemaProperty = SchemaProperty(
                            propertyType = PropertyType.RLM_PROPERTY_TYPE_INT,
                            declaration = declaration,
                            collectionType = CollectionType.NONE
                        )
                        fields[name] = schemaProperty
                        // Not using the default fromPublic/toPublic solution here. We agreed
                        // changing the frontend in the future might incur in several changes to the
                        // implementation so we believe it to be a good decision to postpone making
                        // changes to the current framework until we embark in the next big update.
                        // TL;DR: use custom paths for this datatype as it requires references to
                        // the managed object to which the fields belongs.
                        modifyAccessor(
                            property = schemaProperty,
                            getFunction = getMutableInt,
                            fromRealmValue = null,
                            toPublic = null,
                            setFunction = setValue,
                            fromPublic = null,
                            toRealmValue = null
                        )
                    }
                    propertyType.isRealmAny() -> {
                        logDebug("RealmAny property named ${declaration.name} is ${if (nullable) "" else "not "}nullable")
                        if (!nullable) {
                            logError(
                                "Error in field ${declaration.name} - RealmAny fields must be nullable.",
                                declaration.locationOf()
                            )
                        }
                        val schemaProperty = SchemaProperty(
                            propertyType = PropertyType.RLM_PROPERTY_TYPE_MIXED,
                            declaration = declaration,
                            collectionType = CollectionType.NONE
                        )
                        fields[name] = schemaProperty
                        modifyAccessor(
                            property = schemaProperty,
                            getFunction = getRealmAny,
                            fromRealmValue = null,
                            toPublic = null,
                            setFunction = setValue,
                            fromPublic = null,
                            toRealmValue = null
                        )
                    }

                    propertyType.isByteArray() -> {
                        logDebug("ByteArray property named ${declaration.name} is ${if (nullable) "" else "not "}nullable")
                        val schemaProperty = SchemaProperty(
                            propertyType = PropertyType.RLM_PROPERTY_TYPE_BINARY,
                            declaration = declaration,
                            collectionType = CollectionType.NONE
                        )
                        fields[name] = schemaProperty
                        modifyAccessor(
                            property = schemaProperty,
                            getFunction = getByteArray,
                            fromRealmValue = null,
                            toPublic = null,
                            setFunction = setValue,
                            fromPublic = null,
                            toRealmValue = null
                        )
                    }
                    propertyType.isString() -> {
                        logDebug("String property named ${declaration.name} is ${if (nullable) "" else "not "}nullable")
                        val schemaProperty = SchemaProperty(
                            propertyType = PropertyType.RLM_PROPERTY_TYPE_STRING,
                            declaration = declaration,
                            collectionType = CollectionType.NONE
                        )
                        fields[name] = schemaProperty
                        modifyAccessor(
                            property = schemaProperty,
                            getFunction = getString,
                            fromRealmValue = null,
                            toPublic = null,
                            setFunction = setValue,
                            fromPublic = null,
                            toRealmValue = null
                        )
                    }
                    propertyType.isByte() -> {
                        logDebug("Byte property named ${declaration.name} is ${if (nullable) "" else "not "}nullable")
                        val schemaProperty = SchemaProperty(
                            propertyType = PropertyType.RLM_PROPERTY_TYPE_INT,
                            declaration = declaration,
                            collectionType = CollectionType.NONE
                        )
                        fields[name] = schemaProperty
                        modifyAccessor(
                            property = schemaProperty,
                            getFunction = getLong,
                            fromRealmValue = longToByte,
                            toPublic = null,
                            setFunction = setValue,
                            fromPublic = byteToLong,
                            toRealmValue = null
                        )
                    }
                    propertyType.isChar() -> {
                        logDebug("Char property named ${declaration.name} is ${if (nullable) "" else "not "}nullable")
                        val schemaProperty = SchemaProperty(
                            propertyType = PropertyType.RLM_PROPERTY_TYPE_INT,
                            declaration = declaration,
                            collectionType = CollectionType.NONE
                        )
                        fields[name] = schemaProperty
                        modifyAccessor(
                            property = schemaProperty,
                            getFunction = getLong,
                            fromRealmValue = longToChar,
                            toPublic = null,
                            setFunction = setValue,
                            fromPublic = charToLong,
                            toRealmValue = null
                        )
                    }
                    propertyType.isShort() -> {
                        logDebug("Short property named ${declaration.name} is ${if (nullable) "" else "not "}nullable")
                        val schemaProperty = SchemaProperty(
                            propertyType = PropertyType.RLM_PROPERTY_TYPE_INT,
                            declaration = declaration,
                            collectionType = CollectionType.NONE
                        )
                        fields[name] = schemaProperty
                        modifyAccessor(
                            property = schemaProperty,
                            getFunction = getLong,
                            fromRealmValue = longToShort,
                            toPublic = null,
                            setFunction = setValue,
                            fromPublic = shortToLong,
                            toRealmValue = null
                        )
                    }
                    propertyType.isInt() -> {
                        logDebug("Int property named ${declaration.name} is ${if (nullable) "" else "not "}nullable")
                        val schemaProperty = SchemaProperty(
                            propertyType = PropertyType.RLM_PROPERTY_TYPE_INT,
                            declaration = declaration,
                            collectionType = CollectionType.NONE
                        )
                        fields[name] = schemaProperty
                        modifyAccessor(
                            property = schemaProperty,
                            getFunction = getLong,
                            fromRealmValue = longToInt,
                            toPublic = null,
                            setFunction = setValue,
                            fromPublic = intToLong,
                            toRealmValue = null
                        )
                    }
                    propertyType.isLong() -> {
                        logDebug("Long property named ${declaration.name} is ${if (nullable) "" else "not "}nullable")
                        val schemaProperty = SchemaProperty(
                            propertyType = PropertyType.RLM_PROPERTY_TYPE_INT,
                            declaration = declaration,
                            collectionType = CollectionType.NONE
                        )
                        fields[name] = schemaProperty
                        modifyAccessor(
                            property = schemaProperty,
                            getFunction = getLong,
                            fromRealmValue = null,
                            toPublic = null,
                            setFunction = setValue,
                            fromPublic = null,
                            toRealmValue = null
                        )
                    }
                    propertyType.isBoolean() -> {
                        logDebug("Boolean property named ${declaration.name} is ${if (nullable) "" else "not "}nullable")
                        val schemaProperty = SchemaProperty(
                            propertyType = PropertyType.RLM_PROPERTY_TYPE_BOOL,
                            declaration = declaration,
                            collectionType = CollectionType.NONE
                        )
                        fields[name] = schemaProperty
                        modifyAccessor(
                            property = schemaProperty,
                            getFunction = getBoolean,
                            fromRealmValue = null,
                            toPublic = null,
                            setFunction = setValue,
                            fromPublic = null,
                            toRealmValue = null
                        )
                    }
                    propertyType.isFloat() -> {
                        logDebug("Float property named ${declaration.name} is ${if (nullable) "" else "not "}nullable")
                        val schemaProperty = SchemaProperty(
                            propertyType = PropertyType.RLM_PROPERTY_TYPE_FLOAT,
                            declaration = declaration,
                            collectionType = CollectionType.NONE
                        )
                        fields[name] = schemaProperty
                        modifyAccessor(
                            property = schemaProperty,
                            getFunction = getFloat,
                            fromRealmValue = null,
                            toPublic = null,
                            setFunction = setValue,
                            fromPublic = null,
                            toRealmValue = null
                        )
                    }
                    propertyType.isDouble() -> {
                        logDebug("Double property named ${declaration.name} is ${if (nullable) "" else "not "}nullable")
                        val schemaProperty = SchemaProperty(
                            propertyType = PropertyType.RLM_PROPERTY_TYPE_DOUBLE,
                            declaration = declaration,
                            collectionType = CollectionType.NONE
                        )
                        fields[name] = schemaProperty
                        modifyAccessor(
                            property = schemaProperty,
                            getFunction = getDouble,
                            fromRealmValue = null,
                            toPublic = null,
                            setFunction = setValue,
                            fromPublic = null,
                            toRealmValue = null
                        )
                    }
                    propertyType.isDecimal128() -> {
                        logDebug("Decimal128 property named ${declaration.name} is ${if (nullable) "" else "not "}nullable")
                        val schemaProperty = SchemaProperty(
                            propertyType = PropertyType.RLM_PROPERTY_TYPE_DECIMAL128,
                            declaration = declaration,
                            collectionType = CollectionType.NONE
                        )
                        fields[name] = schemaProperty
                        modifyAccessor(
                            property = schemaProperty,
                            getFunction = getDecimal128,
                            fromRealmValue = null,
                            toPublic = null,
                            setFunction = setValue,
                            fromPublic = null,
                            toRealmValue = null
                        )
                    }
                    propertyType.isEmbeddedLinkingObject() || propertyType.isLinkingObject() -> {
                        getBacklinksTargetPropertyType(declaration)?.let { targetPropertyType ->
                            val sourceType: IrSimpleType = irClass.defaultType

                            targetPropertyType as IrAbstractSimpleType

                            // Validates that backlinks points to a valid type
                            val generic: IrAbstractSimpleType? = targetPropertyType.arguments
                                .getOrNull(0)?.let { argument: IrTypeArgument ->
                                    argument as IrAbstractSimpleType
                                }

                            val isValidTargetType = targetPropertyType.hasSameClassId(sourceType)
                            val isValidCollectionType = targetPropertyType.isRealmList() ||
                                targetPropertyType.isRealmSet() ||
                                targetPropertyType.isRealmDictionary()
                            val isValidGenericType = isValidCollectionType &&
                                generic!!.type.hasSameClassId(sourceType)

                            if (!(isValidTargetType || isValidGenericType)) {
                                val targetPropertyName = getLinkingObjectPropertyName(declaration.backingField!!)
                                logError(
                                    "Error in backlinks field '${declaration.name}' - target property '$targetPropertyName' does not reference '${sourceType.toIrBasedKotlinType().getKotlinTypeFqNameCompat(true)}'.",
                                    declaration.locationOf()
                                )
                            }

                            fields[name] = SchemaProperty(
                                propertyType = PropertyType.RLM_PROPERTY_TYPE_LINKING_OBJECTS,
                                declaration = declaration,
                                collectionType = CollectionType.LIST,
                                coreGenericTypes = listOf(
                                    CoreType(
                                        propertyType = PropertyType.RLM_PROPERTY_TYPE_OBJECT,
                                        nullable = false
                                    )
                                )
                            )
                        }
                    }
                    propertyType.isRealmInstant() -> {
                        logDebug("RealmInstant property named ${declaration.name} is ${if (nullable) "" else "not "}nullable")
                        val schemaProperty = SchemaProperty(
                            propertyType = PropertyType.RLM_PROPERTY_TYPE_TIMESTAMP,
                            declaration = declaration,
                            collectionType = CollectionType.NONE
                        )
                        fields[name] = schemaProperty
                        modifyAccessor(
                            property = schemaProperty,
                            getFunction = getInstant,
                            fromRealmValue = null,
                            toPublic = null,
                            setFunction = setValue,
                            fromPublic = null,
                            toRealmValue = null
                        )
                    }
                    propertyType.isObjectId() -> {
                        logDebug("ObjectId property named ${declaration.name} is ${if (nullable) "" else "not "}nullable")
                        val schemaProperty = SchemaProperty(
                            propertyType = PropertyType.RLM_PROPERTY_TYPE_OBJECT_ID,
                            declaration = declaration,
                            collectionType = CollectionType.NONE
                        )
                        fields[name] = schemaProperty
                        modifyAccessor(
                            property = schemaProperty,
                            getFunction = getObjectId,
                            fromRealmValue = null,
                            toPublic = null,
                            setFunction = setValue,
                            fromPublic = null,
                            toRealmValue = null
                        )
                    }
                    propertyType.isRealmUUID() -> {
                        logDebug("RealmUUID property named ${declaration.name} is ${if (nullable) "" else "not "}nullable")
                        val schemaProperty = SchemaProperty(
                            propertyType = PropertyType.RLM_PROPERTY_TYPE_UUID,
                            declaration = declaration,
                            collectionType = CollectionType.NONE
                        )
                        fields[name] = schemaProperty
                        modifyAccessor(
                            property = schemaProperty,
                            getFunction = getUUID,
                            fromRealmValue = null,
                            toPublic = null,
                            setFunction = setValue,
                            fromPublic = null,
                            toRealmValue = null,
                        )
                    }
                    propertyType.isRealmList() -> {
                        logDebug("RealmList property named ${declaration.name} is ${if (nullable) "" else "not "}nullable")
                        processCollectionField(CollectionType.LIST, fields, name, declaration)
                    }
                    propertyType.isRealmSet() -> {
                        logDebug("RealmSet property named ${declaration.name} is ${if (nullable) "" else "not "}nullable")
                        processCollectionField(CollectionType.SET, fields, name, declaration)
                    }
                    propertyType.isRealmDictionary() -> {
                        logDebug("RealmDictionary property named ${declaration.name} is ${if (nullable) "" else "not "}nullable")
                        processCollectionField(CollectionType.DICTIONARY, fields, name, declaration)
                    }
                    propertyType.isSubtypeOfClass(embeddedRealmObjectInterface) -> {
                        logDebug("Object property named ${declaration.name} is embedded and ${if (nullable) "" else "not "}nullable")
                        val schemaProperty = SchemaProperty(
                            propertyType = PropertyType.RLM_PROPERTY_TYPE_OBJECT,
                            declaration = declaration,
                            collectionType = CollectionType.NONE
                        )
                        fields[name] = schemaProperty
                        modifyAccessor(
                            schemaProperty,
                            getFunction = getObject,
                            fromRealmValue = null,
                            toPublic = null,
                            setFunction = setEmbeddedRealmObject,
                            fromPublic = null,
                            toRealmValue = null
                        )
                    }
                    asymmetricRealmObjectInterface != null && propertyType.isSubtypeOfClass(asymmetricRealmObjectInterface.symbol) -> {
                        // Asymmetric objects must be top-level objects, so any link to one
                        // should be illegal. This will be detected later when creating the
                        // schema methods. So for now, just add the field to the list of schema
                        // properties, but do not modify the accessor.
                        logDebug("Object property named ${declaration.name} is ${if (nullable) "" else "not "}nullable")
                        val schemaProperty = SchemaProperty(
                            propertyType = PropertyType.RLM_PROPERTY_TYPE_OBJECT,
                            declaration = declaration,
                            collectionType = CollectionType.NONE
                        )
                        fields[name] = schemaProperty
                    }
                    propertyType.isSubtypeOfClass(realmObjectInterface) -> {
                        logDebug("Object property named ${declaration.name} is ${if (nullable) "" else "not "}nullable")
                        val schemaProperty = SchemaProperty(
                            propertyType = PropertyType.RLM_PROPERTY_TYPE_OBJECT,
                            declaration = declaration,
                            collectionType = CollectionType.NONE
                        )
                        fields[name] = schemaProperty
                        // Current getObject/setObject has it's own public->storagetype->realmvalue
                        // conversion so bypass any converters in accessors
                        modifyAccessor(
                            property = schemaProperty,
                            getFunction = getObject,
                            fromRealmValue = null,
                            toPublic = null,
                            setFunction = setObject,
                            fromPublic = null,
                            toRealmValue = null
                        )
                    }
                    else -> {
                        logError("Realm does not support persisting properties of this type. Mark the field with `@Ignore` to suppress this error.", declaration.locationOf())
                    }
                }

                return super.visitProperty(declaration)
            }
        })
    }

    private fun processCollectionField(
        collectionType: CollectionType,
        fields: MutableMap,
        name: String,
        declaration: IrProperty
    ) {
        val type: KotlinType = declaration.symbol.owner.toIrBasedDescriptor().type
        if (type.arguments[0] is StarProjectionImpl) {
            logError(
                "Error in field ${declaration.name} - ${collectionType.description} cannot use a '*' projection.",
                declaration.locationOf()
            )
            return
        }
        val collectionGenericType = type.arguments[0].type
        val coreGenericTypes = getCollectionGenericCoreType(collectionType, declaration)

        // Only process field if we got valid generics
        if (coreGenericTypes != null) {
            val genericPropertyType = getPropertyTypeFromKotlinType(collectionGenericType)

            // Only process
            if (genericPropertyType != null) {
                val schemaProperty = SchemaProperty(
                    propertyType = genericPropertyType,
                    declaration = declaration,
                    collectionType = collectionType,
                    coreGenericTypes = listOf(coreGenericTypes)
                )
                fields[name] = schemaProperty
                // TODO OPTIMIZE consider synthetic property generation for lists to cache
                //  reference instead of emitting a new list every time - also for links
                //  see e.g.
                //  if (isManaged()) {
                //      if (io_realm_kotlin_synthetic$myList == null) {
                //          io_realm_kotlin_synthetic$myList = RealmObjectHelper.getList(this, "myList")
                //      }
                //      return io_realm_kotlin_synthetic$myList
                //  } else {
                //      return backing_field
                //  }

                // getCollection/setCollection gets/sets raw collections so it bypasses any converters in accessors
                modifyAccessor(
                    property = schemaProperty,
                    getFunction = when (collectionType) {
                        CollectionType.LIST -> getList
                        CollectionType.SET -> getSet
                        CollectionType.DICTIONARY -> getDictionary
                        else -> throw UnsupportedOperationException("Only collections or dictionaries are supposed to modify the getter for '$name'")
                    },
                    fromRealmValue = null,
                    toPublic = null,
                    setFunction = when (collectionType) {
                        CollectionType.LIST -> setList
                        CollectionType.SET -> setSet
                        CollectionType.DICTIONARY -> setDictionary
                        else -> throw UnsupportedOperationException("Only collections or dictionaries are supposed to modify the setter for '$name'")
                    },
                    fromPublic = null,
                    toRealmValue = null,
                    collectionType = collectionType
                )
            }
        }
    }

    @Suppress("LongParameterList", "LongMethod", "ComplexMethod")
    private fun modifyAccessor(
        property: SchemaProperty,
        getFunction: IrSimpleFunction,
        fromRealmValue: IrSimpleFunction? = null,
        toPublic: IrSimpleFunction? = null,
        setFunction: IrSimpleFunction? = null,
        fromPublic: IrSimpleFunction? = null,
        toRealmValue: IrSimpleFunction? = null,
        collectionType: CollectionType = CollectionType.NONE
    ) {
        val backingField = property.declaration.backingField!!
        val type: IrType? = when (collectionType) {
            CollectionType.NONE -> backingField.type
            CollectionType.LIST,
            CollectionType.SET,
            CollectionType.DICTIONARY -> getCollectionElementType(backingField.type)
        }
        val getter = property.declaration.getter
        val setter = property.declaration.setter
        getter?.apply {
            /**
             * Transform the getter to whether access the managed object or the backing field
             * ```
             * get() {
             *      return this.`io_realm_kotlin_objectReference`?.let { it ->
             *         toPublic(fromRealmValue(it.getValue("propertyName"))
             *      } ?: backingField
             * }
             * ```
             */

            // TODO optimize: we can simplify the code paths in RealmObjectHelper for all
            //  getters if we wrap the calls using the 'getterScope {...}'  function and calling
            //  the converter helper functions for each supported data type. We should
            //  investigate how to use 'IrFunctionExpressionImpl' since it appears to be the
            //  way to go. Until then we can achieve high performance by having one accessor
            //  call per supported storage type.

            origin = IrDeclarationOrigin.GeneratedByPlugin(RealmPluginGeneratorKey)

            body = IrBlockBuilder(
                pluginContext,
                Scope(getter.symbol),
                startOffset = body!!.startOffset,
                endOffset = body!!.endOffset,
            ).irBlockBody {
                val receiver: IrValueParameter = getter.dispatchReceiverParameter!!

                +irReturn(
                    irBlock {
                        val tmp = irTemporary(
                            irCall(
                                objectReferenceProperty.getter!!,
                            ).also {
                                it.dispatchReceiver = irGet(receiver)
                            },
                            nameHint = "objectReference",
                            irType = objectReferenceType,
                        )
                        val managedObjectGetValueCall: IrCall = irCall(
                            callee = getFunction,
                            origin = null
                        ).also {
                            it.dispatchReceiver = irGetObject(realmObjectHelper.symbol)
                        }.apply {
                            if (typeArgumentsCount > 0) {
                                putTypeArgument(0, type)
                            }
                            putValueArgument(0, irGet(objectReferenceType, tmp.symbol))
                            putValueArgument(1, irString(property.persistedName))
                        }
                        val storageValue = fromRealmValue?.let {
                            irCall(callee = it).apply {
                                if (typeArgumentsCount > 0) {
                                    putTypeArgument(0, type)
                                }
                                putValueArgument(0, managedObjectGetValueCall)
                            }
                        } ?: managedObjectGetValueCall
                        val publicValue = toPublic?.let {
                            irCall(callee = toPublic).apply {
                                putValueArgument(0, storageValue)
                            }
                        } ?: storageValue
                        +irIfNull(
                            type = getter.returnType,
                            subject = irGet(objectReferenceType, tmp.symbol),
                            // Unmanaged object, return backing field
                            thenPart = irGetField(irGet(receiver), backingField, backingField.type),
                            // Managed object, return realm value
                            elsePart = publicValue
                        )
                    }
                )
            }
        }

        // Setter function is null when working with immutable properties
        if (setFunction != null) {
            setter?.apply {
                /**
                 * Transform the setter to whether access the managed object or the backing field
                 * ```
                 * set(value) {
                 *      this.`io_realm_kotlin_objectReference`?.let {
                 *          it.setValue("propertyName", toRealmValue(fromPublic(value)))
                 *      } ?: run { backingField = value }
                 * }
                 * ```
                 */

                // TODO optimize: similarly to what is written above about the getters, we could do
                //  something similar for the setters and 'inputScope/inputScopeTracked {...}'.

                origin = IrDeclarationOrigin.GeneratedByPlugin(RealmPluginGeneratorKey)

                body = IrBlockBuilder(
                    pluginContext,
                    Scope(setter.symbol),
                    startOffset = body!!.startOffset,
                    endOffset = body!!.endOffset,
                ).irBlockBody {
                    val receiver: IrValueParameter = setter.dispatchReceiverParameter!!

                    val tmp = irTemporary(
                        irCall(
                            objectReferenceProperty.getter!!,
                        ).also {
                            it.dispatchReceiver = irGet(receiver)
                        },
                        nameHint = "objectReference",
                        irType = objectReferenceType,
                    )
                    val storageValue: IrDeclarationReference = fromPublic?.let {
                        irCall(callee = it).apply {
                            putValueArgument(0, irGet(setter.valueParameters.first()))
                        }
                    } ?: irGet(setter.valueParameters.first())
                    val realmValue: IrDeclarationReference = toRealmValue?.let {
                        irCall(callee = it).apply {
                            if (typeArgumentsCount > 0) {
                                putTypeArgument(0, type)
                            }
                            putValueArgument(0, storageValue)
                        }
                    } ?: storageValue
                    val cinteropCall = irCall(
                        callee = setFunction,
                    ).also {
                        it.dispatchReceiver = irGetObject(realmObjectHelper.symbol)
                    }.apply {
                        if (typeArgumentsCount > 0) {
                            putTypeArgument(0, type)
                        }
                        putValueArgument(0, irGet(objectReferenceType, tmp.symbol))
                        putValueArgument(1, irString(property.persistedName))
                        putValueArgument(2, realmValue)
                    }

                    +irIfNull(
                        type = pluginContext.irBuiltIns.unitType,
                        subject = irGet(objectReferenceType, tmp.symbol),
                        // Unmanaged object, set the backing field
                        thenPart =
                        irSetField(
                            irGet(receiver),
                            backingField.symbol.owner,
                            irGet(setter.valueParameters.first()),
                        ),
                        // Managed object, return realm value
                        elsePart = cinteropCall
                    )
                }
            }
        }
    }

    private fun IrType.isRealmList(): Boolean {
        val propertyClassId: ClassId = this.classIdOrFail()
        val realmListClassId: ClassId? = realmListClass.classId
        return propertyClassId == realmListClassId
    }

    private fun IrType.isRealmSet(): Boolean {
        val propertyClassId: ClassId = this.classIdOrFail()
        val realmSetClassId: ClassId? = realmSetClass.classId
        return propertyClassId == realmSetClassId
    }

    private fun IrType.isRealmDictionary(): Boolean {
        val propertyClassId: ClassId = this.classIdOrFail()
        val realmDictionaryClassId: ClassId? = realmDictionaryClass.classId
        return propertyClassId == realmDictionaryClassId
    }

    private fun IrType.isRealmInstant(): Boolean {
        val propertyClassId: ClassId = this.classIdOrFail()
        val realmInstantClassId: ClassId? = realmInstantClass.classId
        return propertyClassId == realmInstantClassId
    }

    private fun IrType.isLinkingObject(): Boolean {
        val propertyClassId: ClassId = this.classIdOrFail()
        val realmBacklinksClassId: ClassId? = realmBacklinksClass.classId
        return propertyClassId == realmBacklinksClassId
    }

    private fun IrType.isEmbeddedLinkingObject(): Boolean {
        val propertyClassId: ClassId = this.classIdOrFail()
        val realmEmbeddedBacklinksClassId: ClassId? = realmEmbeddedBacklinksClass.classId
        return propertyClassId == realmEmbeddedBacklinksClassId
    }

    private fun IrType.isDecimal128(): Boolean {
        val propertyClassId: ClassId = this.classIdOrFail()
        val objectIdClassId: ClassId? = decimal128Class.classId
        return propertyClassId == objectIdClassId
    }

    private fun IrType.isObjectId(): Boolean {
        val propertyClassId: ClassId = this.classIdOrFail()
        val objectIdClassId: ClassId? = objectIdClass.classId
        return propertyClassId == objectIdClassId
    }

    private fun IrType.hasSameClassId(other: IrType): Boolean {
        val propertyClassId: ClassId = this.classIdOrFail()
        val otherClassId = other.classIdOrFail()
        return propertyClassId == otherClassId
    }

    private fun IrType.isRealmUUID(): Boolean {
        val propertyClassId: ClassId = this.classIdOrFail()
        val realmUUIDClassId: ClassId? = realmUUIDClass.classId
        return propertyClassId == realmUUIDClassId
    }

    fun IrType.isMutableRealmInteger(): Boolean {
        val propertyClassId: ClassId = this.classIdOrFail()
        val mutableRealmIntegerClassId: ClassId? = mutableRealmIntegerClass.classId
        return propertyClassId == mutableRealmIntegerClassId
    }

    fun IrType.isRealmAny(): Boolean {
        val propertyClassId: ClassId = this.classIdOrFail()
        val mutableRealmIntegerClassId: ClassId? = realmAnyClass.classId
        return propertyClassId == mutableRealmIntegerClassId
    }

    @Suppress("ReturnCount", "LongMethod")
    private fun getCollectionGenericCoreType(
        collectionType: CollectionType,
        declaration: IrProperty
    ): CoreType? {
        // Check first if the generic is a subclass of RealmObject
        val descriptorType: KotlinType = declaration.toIrBasedDescriptor().type
        val collectionGenericType: KotlinType = descriptorType.arguments[0].type

        val supertypes = collectionGenericType.constructor.supertypes
        val isEmbedded = inheritsFromRealmObject(supertypes, RealmObjectType.EMBEDDED)

        if (inheritsFromRealmObject(supertypes)) {
            // No embedded objects for sets
            if (collectionType == CollectionType.SET && isEmbedded) {
                logError(
                    "Error in field ${declaration.name} - ${collectionType.description} does not support embedded realm objects element types.",
                    declaration.locationOf()
                )
                return null
            }

            val isNullable = collectionGenericType.isNullable()

            // Lists of objects/embedded objects and sets of object may NOT contain null values, but dictionaries may
            when (collectionType) {
                CollectionType.SET,
                CollectionType.LIST -> {
                    if (isNullable) {
                        logError(
                            "Error in field ${declaration.name} - ${collectionType.description} does not support nullable realm objects element types.",
                            declaration.locationOf()
                        )
                        return null
                    }
                }
                CollectionType.DICTIONARY -> {
                    if (!isNullable) {
                        logError(
                            "Error in field ${declaration.name} - RealmDictionary does not support non-nullable realm objects element types.",
                            declaration.locationOf()
                        )
                        return null
                    }
                }
                else -> throw IllegalArgumentException("Only collections can be processed here.")
            }

            return CoreType(
                propertyType = PropertyType.RLM_PROPERTY_TYPE_OBJECT,
                nullable = isNullable
            )
        }

        // If not a RealmObject, check whether the collection itself is nullable - if so, throw error
        if (descriptorType.isNullable()) {
            logError(
                "Error in field ${declaration.name} - a ${collectionType.description} field cannot be marked as nullable.",
                declaration.locationOf()
            )
            return null
        }

        // Otherwise just return the matching core type present in the declaration
        val genericPropertyType: PropertyType? = getPropertyTypeFromKotlinType(collectionGenericType)
        return if (genericPropertyType == null) {
            logError(
                "Unsupported type for ${collectionType.description}: '${collectionGenericType.getKotlinTypeFqNameCompat(true)
                }'",
                declaration.locationOf()
            )
            null
        } else if (genericPropertyType == PropertyType.RLM_PROPERTY_TYPE_MIXED && !collectionGenericType.isNullable()) {
            logError(
                "Unsupported type for ${collectionType.description}: Only '${collectionType.description}' is supported.",
                declaration.locationOf()
            )
            return null
        } else {
            CoreType(
                propertyType = genericPropertyType,
                nullable = collectionGenericType.isNullable()
            )
        }
    }

    // TODO do the lookup only once
    @Suppress("ComplexMethod")
    private fun getPropertyTypeFromKotlinType(type: KotlinType): PropertyType? {
        return type.constructor.declarationDescriptor
            ?.name
            ?.let { identifier ->
                when (identifier.toString()) {
                    "Byte" -> PropertyType.RLM_PROPERTY_TYPE_INT
                    "Char" -> PropertyType.RLM_PROPERTY_TYPE_INT
                    "Short" -> PropertyType.RLM_PROPERTY_TYPE_INT
                    "Int" -> PropertyType.RLM_PROPERTY_TYPE_INT
                    "Long" -> PropertyType.RLM_PROPERTY_TYPE_INT
                    "Boolean" -> PropertyType.RLM_PROPERTY_TYPE_BOOL
                    "Float" -> PropertyType.RLM_PROPERTY_TYPE_FLOAT
                    "Double" -> PropertyType.RLM_PROPERTY_TYPE_DOUBLE
                    "String" -> PropertyType.RLM_PROPERTY_TYPE_STRING
                    "RealmInstant" -> PropertyType.RLM_PROPERTY_TYPE_TIMESTAMP
                    "ObjectId" -> PropertyType.RLM_PROPERTY_TYPE_OBJECT_ID
                    "BsonObjectId" -> PropertyType.RLM_PROPERTY_TYPE_OBJECT_ID
                    "BsonDecimal128" -> PropertyType.RLM_PROPERTY_TYPE_DECIMAL128
                    "RealmUUID" -> PropertyType.RLM_PROPERTY_TYPE_UUID
                    "ByteArray" -> PropertyType.RLM_PROPERTY_TYPE_BINARY
                    "RealmAny" -> PropertyType.RLM_PROPERTY_TYPE_MIXED
                    else ->
                        if (inheritsFromRealmObject(type.supertypes())) {
                            PropertyType.RLM_PROPERTY_TYPE_OBJECT
                        } else {
                            null
                        }
                }
            }
    }

    // Check if the class in question inherits from RealmObject, EmbeddedRealmObject or either
    private fun inheritsFromRealmObject(
        supertypes: Collection,
        objectType: RealmObjectType = RealmObjectType.EITHER
    ): Boolean = supertypes.any {
        val objectFqNames: Set = when (objectType) {
            RealmObjectType.OBJECT -> realmObjectInterfaceFqNames
            RealmObjectType.EMBEDDED -> realmEmbeddedObjectInterfaceFqNames
            RealmObjectType.EITHER -> realmObjectInterfaceFqNames + realmEmbeddedObjectInterfaceFqNames
        }
        it.constructor.declarationDescriptor?.classId in objectFqNames
    }
}

private enum class RealmObjectType {
    OBJECT, EMBEDDED, EITHER
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy