diff --git a/CHANGELOG.md b/CHANGELOG.md index d56a375bb1..0c0f679753 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ * None. ### Enhancements -* None. +* Support for experimental K2-compilation with `kotlin.experimental.tryK2=true`. (Issue [#1483](https://github.com/realm/realm-kotlin/issues/1483)) ### Fixed * None. @@ -13,7 +13,7 @@ * File format: Generates Realms with file format v23. * Realm Studio 13.0.0 or above is required to open Realms created by this version. * This release is compatible with the following Kotlin releases: - * Kotlin 1.8.0 and above. The K2 compiler is not supported yet. + * Kotlin 1.9.0 and above. Support for experimental K2-compilation with `kotlin.experimental.tryK2=true`. * Ktor 2.1.2 and above. * Coroutines 1.7.0 and above. * AtomicFu 0.18.3 and above. @@ -22,6 +22,7 @@ * Minimum Gradle version: 6.8.3. * Minimum Android Gradle Plugin version: 4.1.3. * Minimum Android SDK: 16. +* Minimum R8: 8.0.34. ### Internal * None. diff --git a/Jenkinsfile b/Jenkinsfile index c3ff69b098..af83a48421 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -247,6 +247,7 @@ pipeline { when { expression { runTests } } steps { testAndCollect("integration-tests/gradle-plugin-test", "integrationTest") + testAndCollect("integration-tests/gradle-plugin-test", "-Pkotlin.experimental.tryK2=true integrationTest") } } stage('Tests Android Sample App') { @@ -268,6 +269,7 @@ pipeline { when { expression { runTests } } steps { testAndCollect("examples/realm-java-compatibility", "connectedAndroidTest") + testAndCollect("examples/realm-java-compatibility", "-Pkotlin.experimental.tryK2=true connectedAndroidTest") } } stage('Track build metrics') { diff --git a/buildSrc/src/main/kotlin/Config.kt b/buildSrc/src/main/kotlin/Config.kt index 161cda7944..4778352d30 100644 --- a/buildSrc/src/main/kotlin/Config.kt +++ b/buildSrc/src/main/kotlin/Config.kt @@ -103,7 +103,7 @@ object Versions { const val buildToolsVersion = "33.0.0" const val buildTools = "7.3.1" // https://maven.google.com/web/index.html?q=gradle#com.android.tools.build:gradle const val ndkVersion = "23.2.8568313" - const val r8 = "4.0.48" // See https://developer.android.com/build/kotlin-support + const val r8 = "8.0.34" // See https://developer.android.com/build/kotlin-support } const val androidxBenchmarkPlugin = "1.2.0-alpha12" // https://maven.google.com/web/index.html#androidx.benchmark:androidx.benchmark.gradle.plugin const val androidxStartup = "1.1.1" // https://maven.google.com/web/index.html?q=startup#androidx.startup:startup-runtime @@ -119,14 +119,14 @@ object Versions { const val coroutines = "1.7.0" // https://mvnrepository.com/artifact/org.jetbrains.kotlinx/kotlinx-coroutines-core const val datetime = "0.4.0" // https://github.com/Kotlin/kotlinx-datetime const val detektPlugin = "1.22.0-RC2" // https://github.com/detekt/detekt - const val dokka = "1.6.0" // https://github.com/Kotlin/dokka + const val dokka = "1.9.0" // https://github.com/Kotlin/dokka const val gradlePluginPublishPlugin = "0.15.0" // https://plugins.gradle.org/plugin/com.gradle.plugin-publish const val jmh = "1.34" // https://github.com/openjdk/jmh const val jmhPlugin = "0.6.6" // https://github.com/melix/jmh-gradle-plugin const val junit = "4.13.2" // https://mvnrepository.com/artifact/junit/junit const val kbson = "0.3.0" // https://github.com/mongodb/kbson // When updating the Kotlin version, also remember to update /examples/min-android-sample/build.gradle.kts - const val kotlin = "1.8.21" // https://github.com/JetBrains/kotlin and https://kotlinlang.org/docs/releases.html#release-details + const val kotlin = "1.9.0" // https://github.com/JetBrains/kotlin and https://kotlinlang.org/docs/releases.html#release-details const val kotlinJvmTarget = "1.8" // Which JVM bytecode version is kotlin compiled to. const val latestKotlin = "1.9.20-Beta" // https://kotlinlang.org/docs/eap.html#build-details const val kotlinCompileTesting = "1.5.0" // https://github.com/tschuchortdev/kotlin-compile-testing @@ -136,7 +136,7 @@ object Versions { const val nexusPublishPlugin = "1.1.0" // https://github.com/gradle-nexus/publish-plugin const val okio = "3.2.0" // https://square.github.io/okio/#releases const val relinker = "1.4.5" // https://github.com/KeepSafe/ReLinker - const val serialization = "1.4.0" // https://kotlinlang.org/docs/releases.html#release-details + const val serialization = "1.6.0" // https://kotlinlang.org/docs/releases.html#release-details const val shadowJar = "6.1.0" // https://mvnrepository.com/artifact/com.github.johnrengelman.shadow/com.github.johnrengelman.shadow.gradle.plugin?repo=gradle-plugins val sourceCompatibilityVersion = JavaVersion.VERSION_1_8 // Language level of any Java source code. val targetCompatibilityVersion = JavaVersion.VERSION_1_8 // Version of generated JVM bytecode from Java files. diff --git a/examples/kmm-sample/shared/build.gradle.kts b/examples/kmm-sample/shared/build.gradle.kts index 64fae846d6..97dfb9dbfc 100644 --- a/examples/kmm-sample/shared/build.gradle.kts +++ b/examples/kmm-sample/shared/build.gradle.kts @@ -64,7 +64,7 @@ kotlin { implementation("com.google.android.material:material:1.2.0") } } - val androidTest by getting { + val androidInstrumentedTest by getting { dependencies { implementation(kotlin("test-junit")) implementation("junit:junit:4.12") diff --git a/integration-tests/gradle-plugin-test/multi-platform/src/nativeTest/kotlin/io/realm/test/multiplatform/util/platform/PlatformUtils.kt b/integration-tests/gradle-plugin-test/multi-platform/src/nativeTest/kotlin/io/realm/test/multiplatform/util/platform/PlatformUtils.kt index 26b1dabdab..9b5b5b4fca 100644 --- a/integration-tests/gradle-plugin-test/multi-platform/src/nativeTest/kotlin/io/realm/test/multiplatform/util/platform/PlatformUtils.kt +++ b/integration-tests/gradle-plugin-test/multi-platform/src/nativeTest/kotlin/io/realm/test/multiplatform/util/platform/PlatformUtils.kt @@ -15,8 +15,11 @@ * limitations under the License. */ +@file:OptIn(ExperimentalForeignApi::class) + package io.realm.test.multiplatform.util.platform +import kotlinx.cinterop.ExperimentalForeignApi import kotlinx.cinterop.cstr actual object PlatformUtils { diff --git a/packages/cinterop/build.gradle.kts b/packages/cinterop/build.gradle.kts index 8cbeb83f8e..93cdb46e9e 100644 --- a/packages/cinterop/build.gradle.kts +++ b/packages/cinterop/build.gradle.kts @@ -271,6 +271,7 @@ kotlin { compilations.all { kotlinOptions { freeCompilerArgs += listOf("-opt-in=kotlin.ExperimentalUnsignedTypes") + freeCompilerArgs += listOf("-opt-in=kotlinx.cinterop.ExperimentalForeignApi") } } } diff --git a/packages/cinterop/src/nativeDarwin/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt b/packages/cinterop/src/nativeDarwin/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt index 4ac37661cb..213be632f4 100644 --- a/packages/cinterop/src/nativeDarwin/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt +++ b/packages/cinterop/src/nativeDarwin/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt @@ -1985,7 +1985,7 @@ actual object RealmInterop { realm_wrapper.realm_app_get_all_users( app.cptr(), null, - 0, + 0UL, capacityCount.ptr ) ) diff --git a/packages/cinterop/src/nativeDarwinTest/kotlin/io/realm/kotlin/test/CinteropTest.kt b/packages/cinterop/src/nativeDarwinTest/kotlin/io/realm/kotlin/test/CinteropTest.kt index b7f3e11c5f..b2733215a0 100644 --- a/packages/cinterop/src/nativeDarwinTest/kotlin/io/realm/kotlin/test/CinteropTest.kt +++ b/packages/cinterop/src/nativeDarwinTest/kotlin/io/realm/kotlin/test/CinteropTest.kt @@ -136,14 +136,14 @@ class CinteropTest { classes[0].apply { name = "foo".cstr.ptr primary_key = "".cstr.ptr - num_properties = 1.toULong() - num_computed_properties = 0.toULong() + num_properties = 1UL + num_computed_properties = 0UL flags = RLM_CLASS_NORMAL.toInt() } val classProperties: CPointer>> = cValuesOf(prop_1_1.ptr).ptr - val realmSchemaNew = realm_schema_new(classes, 1.toULong(), classProperties) + val realmSchemaNew = realm_schema_new(classes, 1UL, classProperties) assertNoError() assertTrue( @@ -157,7 +157,7 @@ class CinteropTest { realm_config_set_path(config, "c_api_test.realm") realm_config_set_schema(config, realmSchemaNew) realm_config_set_schema_mode(config, realm_schema_mode_e.RLM_SCHEMA_MODE_AUTOMATIC) - realm_config_set_schema_version(config, 1) + realm_config_set_schema_version(config, 1UL) val realm: CPointer? = realm_open(config) assertEquals(1U, realm_get_num_classes(realm)) diff --git a/packages/library-base/build.gradle.kts b/packages/library-base/build.gradle.kts index d5ca3b5ffc..1a75c1b893 100644 --- a/packages/library-base/build.gradle.kts +++ b/packages/library-base/build.gradle.kts @@ -128,7 +128,12 @@ kotlin { // name and build type variant as a suffix, this default behaviour can cause mismatch at runtime https://github.com/realm/realm-kotlin/issues/621 tasks.withType().configureEach { kotlinOptions { - freeCompilerArgs = listOf("-module-name", "io.realm.kotlin.library") + freeCompilerArgs += listOf("-module-name", "io.realm.kotlin.library") + } +} +tasks.withType().all { + kotlinOptions { + freeCompilerArgs += listOf("-opt-in=kotlinx.cinterop.ExperimentalForeignApi") } } diff --git a/packages/library-sync/src/androidMain/kotlin/io/realm/kotlin/mongodb/internal/RealmSyncInitializer.kt b/packages/library-sync/src/androidMain/kotlin/io/realm/kotlin/mongodb/internal/RealmSyncInitializer.kt index f5bed0c72c..fabe9f7461 100644 --- a/packages/library-sync/src/androidMain/kotlin/io/realm/kotlin/mongodb/internal/RealmSyncInitializer.kt +++ b/packages/library-sync/src/androidMain/kotlin/io/realm/kotlin/mongodb/internal/RealmSyncInitializer.kt @@ -35,7 +35,7 @@ import io.realm.kotlin.log.RealmLog * An **initializer** for Sync specific functionality that does not fit into the `RealmInitializer` * in cinterop.o allow Realm to access context properties. */ -class RealmSyncInitializer : Initializer { +internal class RealmSyncInitializer : Initializer { companion object { @Suppress("DEPRECATION") // Should only be called below API 21 diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MutableSubscriptionSetImpl.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MutableSubscriptionSetImpl.kt index b4059ffa1f..ac56ccd5f3 100644 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MutableSubscriptionSetImpl.kt +++ b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MutableSubscriptionSetImpl.kt @@ -87,7 +87,7 @@ internal class MutableSubscriptionSetImpl( return result } - @Suppress("invisible_member") + @Suppress("invisible_member", "invisible_reference") override fun removeAll(type: KClass): Boolean { var result = false val objectType = io.realm.kotlin.internal.platform.realmObjectCompanionOrThrow(type).`io_realm_kotlin_className` diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/SubscriptionImpl.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/SubscriptionImpl.kt index d908305357..8d8b9f609d 100644 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/SubscriptionImpl.kt +++ b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/SubscriptionImpl.kt @@ -42,7 +42,7 @@ internal class SubscriptionImpl( // Trim the query to match the output of RealmQuery.description() override val queryDescription: String = RealmInterop.realm_sync_subscription_query_string(nativePointer).trim() - @Suppress("invisible_member") + @Suppress("invisible_member", "invisible_reference") override fun asQuery(type: KClass): RealmQuery { // TODO Check for invalid combinations of Realm and type once we properly support // DynamicRealm diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/SyncConfigurationImpl.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/SyncConfigurationImpl.kt index 23d689a20d..1a06d2a32f 100644 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/SyncConfigurationImpl.kt +++ b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/SyncConfigurationImpl.kt @@ -222,7 +222,7 @@ internal class SyncConfigurationImpl( initializerHelper.onSyncError(session, frozenAppPointer, error) } } catch (ex: Exception) { - @Suppress("invisible_member") + @Suppress("invisible_member", "invisible_reference") RealmLog.error("Error thrown and ignored in `onManualResetFallback`: $ex") } } else { diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/SyncSessionImpl.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/SyncSessionImpl.kt index 6ce5e305c6..7a9d3416f6 100644 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/SyncSessionImpl.kt +++ b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/SyncSessionImpl.kt @@ -139,7 +139,7 @@ internal open class SyncSessionImpl( } } - @Suppress("invisible_member") // To be able to use RealmImpl.scopedFlow from library-base + @Suppress("invisible_member", "invisible_reference") // To be able to use RealmImpl.scopedFlow from library-base override fun connectionStateAsFlow(): Flow = realm.scopedFlow { callbackFlow { val token: AtomicRef = kotlinx.atomicfu.atomic(NO_OP_NOTIFICATION_TOKEN) diff --git a/packages/plugin-compiler/src/main/kotlin/io/realm/kotlin/compiler/Identifiers.kt b/packages/plugin-compiler/src/main/kotlin/io/realm/kotlin/compiler/Identifiers.kt index c4d1a87176..ce6afd3774 100644 --- a/packages/plugin-compiler/src/main/kotlin/io/realm/kotlin/compiler/Identifiers.kt +++ b/packages/plugin-compiler/src/main/kotlin/io/realm/kotlin/compiler/Identifiers.kt @@ -26,6 +26,10 @@ import org.jetbrains.kotlin.name.Name internal object Names { const val REALM_SYNTHETIC_PROPERTY_PREFIX = "io_realm_kotlin_" + val REALM_OBJECT: Name = Name.identifier("RealmObject") + val EMBEDDED_REALM_OBJECT: Name = Name.identifier("EmbeddedRealmObject") + val ASYMMETRIC_REALM_OBJECT: Name = Name.identifier("AsymmetricRealmObject") + val REALM_OBJECT_COMPANION_CLASS_MEMBER: Name = Name.identifier("${REALM_SYNTHETIC_PROPERTY_PREFIX}class") val REALM_OBJECT_COMPANION_CLASS_NAME_MEMBER: Name = @@ -40,6 +44,9 @@ internal object Names { Name.identifier("${REALM_SYNTHETIC_PROPERTY_PREFIX}schema") val REALM_OBJECT_COMPANION_NEW_INSTANCE_METHOD = Name.identifier("${REALM_SYNTHETIC_PROPERTY_PREFIX}newInstance") + val REALM_OBJECT_TO_STRING_METHOD = Name.identifier("toString") + val REALM_OBJECT_EQUALS = Name.identifier("equals") + val REALM_OBJECT_HASH_CODE = Name.identifier("hashCode") val SET = Name.special("") @@ -90,11 +97,12 @@ internal object FqNames { val PACKAGE_KBSON = FqName("org.mongodb.kbson") val PACKAGE_KOTLIN_COLLECTIONS = FqName("kotlin.collections") val PACKAGE_KOTLIN_REFLECT = FqName("kotlin.reflect") - val PACKAGE_TYPES = FqName("io.realm.kotlin.types") + val PACKAGE_TYPES: FqName = FqName("io.realm.kotlin.types") val PACKAGE_REALM_INTEROP = FqName("io.realm.kotlin.internal.interop") val PACKAGE_REALM_INTERNAL = FqName("io.realm.kotlin.internal") val PACKAGE_MONGODB = FqName("io.realm.kotlin.mongodb") val PACKAGE_MONGODB_INTERNAL = FqName("io.realm.kotlin.mongodb.internal") + val APP_CONFIGURATION_BUILDER = FqName("AppConfiguration.Builder") } object ClassIds { @@ -160,5 +168,5 @@ object ClassIds { val APP_IMPL = ClassId(PACKAGE_MONGODB_INTERNAL, Name.identifier("AppImpl")) val APP_CONFIGURATION = ClassId(PACKAGE_MONGODB, Name.identifier("AppConfiguration")) val APP_CONFIGURATION_IMPL = ClassId(PACKAGE_MONGODB_INTERNAL, Name.identifier("AppConfigurationImpl")) - val APP_CONFIGURATION_BUILDER = ClassId(FqName("io.realm.kotlin.mongodb.AppConfiguration"), FqName("Builder"), true) + val APP_CONFIGURATION_BUILDER = ClassId(PACKAGE_MONGODB, FqNames.APP_CONFIGURATION_BUILDER, false) } diff --git a/packages/plugin-compiler/src/main/kotlin/io/realm/kotlin/compiler/IrUtils.kt b/packages/plugin-compiler/src/main/kotlin/io/realm/kotlin/compiler/IrUtils.kt index 7c5d1f5f13..01953e72c7 100644 --- a/packages/plugin-compiler/src/main/kotlin/io/realm/kotlin/compiler/IrUtils.kt +++ b/packages/plugin-compiler/src/main/kotlin/io/realm/kotlin/compiler/IrUtils.kt @@ -22,6 +22,10 @@ import io.realm.kotlin.compiler.ClassIds.EMBEDDED_OBJECT_INTERFACE import io.realm.kotlin.compiler.ClassIds.KOTLIN_COLLECTIONS_LISTOF import io.realm.kotlin.compiler.ClassIds.PERSISTED_NAME_ANNOTATION import io.realm.kotlin.compiler.ClassIds.REALM_OBJECT_INTERFACE +import io.realm.kotlin.compiler.FqNames.PACKAGE_TYPES +import io.realm.kotlin.compiler.Names.ASYMMETRIC_REALM_OBJECT +import io.realm.kotlin.compiler.Names.EMBEDDED_REALM_OBJECT +import io.realm.kotlin.compiler.Names.REALM_OBJECT import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext import org.jetbrains.kotlin.backend.common.lower.DeclarationIrBuilder import org.jetbrains.kotlin.cli.common.messages.CompilerMessageLocationWithRange @@ -30,9 +34,15 @@ import org.jetbrains.kotlin.com.intellij.openapi.util.text.StringUtil import org.jetbrains.kotlin.com.intellij.psi.PsiElement import org.jetbrains.kotlin.com.intellij.psi.PsiElementVisitor import org.jetbrains.kotlin.descriptors.ClassDescriptor +import org.jetbrains.kotlin.descriptors.ClassKind import org.jetbrains.kotlin.descriptors.DescriptorVisibilities import org.jetbrains.kotlin.descriptors.Modality import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor +import org.jetbrains.kotlin.fir.symbols.SymbolInternals +import org.jetbrains.kotlin.fir.symbols.impl.FirClassSymbol +import org.jetbrains.kotlin.fir.types.FirResolvedTypeRef +import org.jetbrains.kotlin.fir.types.FirUserTypeRef +import org.jetbrains.kotlin.fir.types.classId import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET import org.jetbrains.kotlin.ir.builders.IrBlockBodyBuilder import org.jetbrains.kotlin.ir.builders.IrBlockBuilder @@ -105,6 +115,7 @@ 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.psi.stubs.elements.KtStubElementTypes import org.jetbrains.kotlin.psi.stubs.elements.KtStubElementTypes.SUPER_TYPE_LIST import org.jetbrains.kotlin.resolve.DescriptorUtils import org.jetbrains.kotlin.types.KotlinType @@ -141,10 +152,9 @@ val anyRealmObjectInterfacesFqNames = realmObjectInterfaceFqNames + realmEmbedde fun IrType.classIdOrFail(): ClassId = getClass()?.classId ?: error("Can't get classId of ${render()}") -inline fun ClassDescriptor.hasInterfacePsi(interfaces: Set): Boolean { - // Using PSI to find super types to avoid cyclic reference (see https://github.com/realm/realm-kotlin/issues/339) +inline fun PsiElement.hasInterface(interfaces: Set): Boolean { var hasRealmObjectAsSuperType = false - this.findPsi()?.acceptChildren(object : PsiElementVisitor() { + this.acceptChildren(object : PsiElementVisitor() { override fun visitElement(element: PsiElement) { if (element.node.elementType == SUPER_TYPE_LIST) { // Check supertypes for classes with Embbeded/RealmObject as generics and remove @@ -169,6 +179,10 @@ inline fun ClassDescriptor.hasInterfacePsi(interfaces: Set): Boolean { return hasRealmObjectAsSuperType } +inline fun ClassDescriptor.hasInterfacePsi(interfaces: Set): Boolean { + // Using PSI to find super types to avoid cyclic reference (see https://github.com/realm/realm-kotlin/issues/339) + return this.findPsi()?.hasInterface(interfaces) ?: false +} // Do to the way PSI works, it can be a bit tricky to uniquely identify when the Realm Kotlin // RealmObject interface is used. For that reason, once we have determined a match for RealmObject, @@ -186,6 +200,34 @@ val ClassDescriptor.isEmbeddedRealmObject: Boolean val ClassDescriptor.isBaseRealmObject: Boolean get() = this.hasInterfacePsi(realmObjectPsiNames + embeddedRealmObjectPsiNames + asymmetricRealmObjectPsiNames) && !this.hasInterfacePsi(realmJavaObjectPsiNames) +val realmObjectTypes: Set = setOf(REALM_OBJECT, EMBEDDED_REALM_OBJECT, ASYMMETRIC_REALM_OBJECT) +val realmObjectClassIds = realmObjectTypes.map { name -> ClassId(PACKAGE_TYPES, name) } + +// This is the K2 equivalent of our PSI hack to determine if a symbol has a RealmObject base class. +// There is currently no way to determine this within the resolved type system and there is +// probably no such option around the corner. +// https://kotlinlang.slack.com/archives/C03PK0PE257/p1694599154558669 +@OptIn(SymbolInternals::class) +val FirClassSymbol<*>.isBaseRealmObject: Boolean + get() = this.classKind == ClassKind.CLASS && + this.fir.superTypeRefs.any { typeRef -> + when (typeRef) { + // In SUPERTYPES stage + is FirUserTypeRef -> { + typeRef.qualifier.last().name in realmObjectTypes && + // Disregard constructor invocations as that means that it is a Realm Java class + !( + typeRef.source?.run { treeStructure.getParent(lighterASTNode) } + ?.tokenType?.let { it == KtStubElementTypes.CONSTRUCTOR_CALLEE } + ?: false + ) + } + // After SUPERTYPES stage + is FirResolvedTypeRef -> typeRef.type.classId in realmObjectClassIds + else -> false + } + } + // JetBrains already have a method `fun IrAnnotationContainer.hasAnnotation(symbol: IrClassSymbol)` // It is unclear exactly what the difference is and how to get a ClassSymbol from a ClassId, // so for now just work around it. 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 5342091838..c9dc1af6a8 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 @@ -51,8 +51,8 @@ class RealmModelSyntheticMethodsExtension : SyntheticResolveExtension { !isNestedInRealmModelClass(thisDescriptor) && /* do not override nested class methods */ result.isEmpty() /* = no method has been declared in the current class */ ) { - when (name.identifier) { - "toString" -> { + when (name) { + Names.REALM_OBJECT_TO_STRING_METHOD -> { result.add( createMethod( classDescriptor = thisDescriptor, @@ -62,7 +62,7 @@ class RealmModelSyntheticMethodsExtension : SyntheticResolveExtension { ) ) } - "equals" -> { + Names.REALM_OBJECT_EQUALS -> { result.add( createMethod( classDescriptor = thisDescriptor, @@ -72,7 +72,7 @@ class RealmModelSyntheticMethodsExtension : SyntheticResolveExtension { ) ) } - "hashCode" -> { + Names.REALM_OBJECT_HASH_CODE -> { result.add( createMethod( classDescriptor = thisDescriptor, 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 ed03b01688..388d647a8e 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 @@ -17,6 +17,7 @@ package io.realm.kotlin.compiler import com.google.auto.service.AutoService +import io.realm.kotlin.compiler.fir.model.RealmModelRegistrar import org.jetbrains.kotlin.backend.common.extensions.IrGenerationExtension import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys import org.jetbrains.kotlin.cli.common.messages.MessageCollector @@ -25,6 +26,7 @@ import org.jetbrains.kotlin.com.intellij.openapi.extensions.LoadingOrder import org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar import org.jetbrains.kotlin.compiler.plugin.ExperimentalCompilerApi import org.jetbrains.kotlin.config.CompilerConfiguration +import org.jetbrains.kotlin.fir.extensions.FirExtensionRegistrarAdapter import org.jetbrains.kotlin.resolve.extensions.SyntheticResolveExtension /** @@ -76,6 +78,11 @@ class Registrar : ComponentRegistrar { LoadingOrder.LAST, project ) + + // K2: Register extension that modifies the API similarly to the above two + // SyntheticResolveExtensions + FirExtensionRegistrarAdapter.registerExtension(project, RealmModelRegistrar()) + // Adds RealmObjectInternal properties, rewires accessors and adds static companion // properties and methods getExtensionPoint(IrGenerationExtension.extensionPointName).registerExtension( diff --git a/packages/plugin-compiler/src/main/kotlin/io/realm/kotlin/compiler/fir/RealmPluginGeneratorKey.kt b/packages/plugin-compiler/src/main/kotlin/io/realm/kotlin/compiler/fir/RealmPluginGeneratorKey.kt new file mode 100644 index 0000000000..7e39509fc6 --- /dev/null +++ b/packages/plugin-compiler/src/main/kotlin/io/realm/kotlin/compiler/fir/RealmPluginGeneratorKey.kt @@ -0,0 +1,28 @@ +/* + * Copyright 2023 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.fir + +import org.jetbrains.kotlin.GeneratedDeclarationKey + +/** + * Key to mark all additions made by our compile plugin. + */ +object RealmPluginGeneratorKey : GeneratedDeclarationKey() { + override fun toString(): String { + return "RealmApiGenerator" + } +} diff --git a/packages/plugin-compiler/src/main/kotlin/io/realm/kotlin/compiler/fir/model/CompanionExtension.kt b/packages/plugin-compiler/src/main/kotlin/io/realm/kotlin/compiler/fir/model/CompanionExtension.kt new file mode 100644 index 0000000000..0ec41e8eb7 --- /dev/null +++ b/packages/plugin-compiler/src/main/kotlin/io/realm/kotlin/compiler/fir/model/CompanionExtension.kt @@ -0,0 +1,113 @@ +/* + * Copyright 2023 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.fir.model + +import io.realm.kotlin.compiler.Names +import io.realm.kotlin.compiler.fir.RealmPluginGeneratorKey +import io.realm.kotlin.compiler.isBaseRealmObject +import org.jetbrains.kotlin.fir.FirSession +import org.jetbrains.kotlin.fir.analysis.checkers.getContainingClassSymbol +import org.jetbrains.kotlin.fir.declarations.utils.isCompanion +import org.jetbrains.kotlin.fir.extensions.FirDeclarationGenerationExtension +import org.jetbrains.kotlin.fir.extensions.MemberGenerationContext +import org.jetbrains.kotlin.fir.extensions.NestedClassGenerationContext +import org.jetbrains.kotlin.fir.plugin.createCompanionObject +import org.jetbrains.kotlin.fir.plugin.createDefaultPrivateConstructor +import org.jetbrains.kotlin.fir.plugin.createMemberFunction +import org.jetbrains.kotlin.fir.symbols.impl.FirClassLikeSymbol +import org.jetbrains.kotlin.fir.symbols.impl.FirClassSymbol +import org.jetbrains.kotlin.fir.symbols.impl.FirConstructorSymbol +import org.jetbrains.kotlin.fir.symbols.impl.FirNamedFunctionSymbol +import org.jetbrains.kotlin.fir.symbols.impl.FirRegularClassSymbol +import org.jetbrains.kotlin.name.CallableId +import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.name.SpecialNames + +class CompanionExtension(session: FirSession) : FirDeclarationGenerationExtension(session) { + override fun getNestedClassifiersNames( + classSymbol: FirClassSymbol<*>, + context: NestedClassGenerationContext + ): Set { + val isRealmObject = classSymbol.isBaseRealmObject + return if (isRealmObject) { + setOf(SpecialNames.DEFAULT_NAME_FOR_COMPANION_OBJECT) + } else { + emptySet() + } + } + + override fun generateNestedClassLikeDeclaration( + owner: FirClassSymbol<*>, + name: Name, + context: NestedClassGenerationContext + ): FirClassLikeSymbol<*>? { + // Only generate new companion if class does not have one already + val companion = (owner as? FirRegularClassSymbol)?.companionObjectSymbol + return if (companion == null && owner.isBaseRealmObject) { + createCompanionObject(owner, RealmPluginGeneratorKey).symbol + } else { null } + } + + override fun getCallableNamesForClass( + classSymbol: FirClassSymbol<*>, + context: MemberGenerationContext + ): Set { + if (classSymbol.isCompanion && (classSymbol.getContainingClassSymbol(session) as? FirClassSymbol<*>)?.isBaseRealmObject == true) { + return setOf( + Names.REALM_OBJECT_COMPANION_SCHEMA_METHOD, + Names.REALM_OBJECT_COMPANION_NEW_INSTANCE_METHOD, + SpecialNames.INIT, // If from our own plugin remember to generate a default constructor + ) + } + return emptySet() + } + + override fun generateFunctions( + callableId: CallableId, + context: MemberGenerationContext? + ): List { + val owner = context?.owner ?: return emptyList() + return when (callableId.callableName) { + Names.REALM_OBJECT_COMPANION_SCHEMA_METHOD -> + listOf( + createMemberFunction( + owner, + RealmPluginGeneratorKey, + callableId.callableName, + session.builtinTypes.anyType.type, + ).symbol + ) + + Names.REALM_OBJECT_COMPANION_NEW_INSTANCE_METHOD -> + listOf( + createMemberFunction( + owner, + RealmPluginGeneratorKey, + callableId.callableName, + session.builtinTypes.anyType.type + ).symbol + ) + + else -> emptyList() + } + } + + override fun generateConstructors(context: MemberGenerationContext): List { + val constructor = createDefaultPrivateConstructor(context.owner, RealmPluginGeneratorKey) + return listOf(constructor.symbol) + } +} diff --git a/packages/plugin-compiler/src/main/kotlin/io/realm/kotlin/compiler/fir/model/ObjectExtension.kt b/packages/plugin-compiler/src/main/kotlin/io/realm/kotlin/compiler/fir/model/ObjectExtension.kt new file mode 100644 index 0000000000..6869c6243f --- /dev/null +++ b/packages/plugin-compiler/src/main/kotlin/io/realm/kotlin/compiler/fir/model/ObjectExtension.kt @@ -0,0 +1,101 @@ +/* + * Copyright 2023 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.fir.model + +import io.realm.kotlin.compiler.Names +import io.realm.kotlin.compiler.fir.RealmPluginGeneratorKey +import io.realm.kotlin.compiler.isBaseRealmObject +import org.jetbrains.kotlin.descriptors.Modality +import org.jetbrains.kotlin.fir.FirSession +import org.jetbrains.kotlin.fir.extensions.FirDeclarationGenerationExtension +import org.jetbrains.kotlin.fir.extensions.MemberGenerationContext +import org.jetbrains.kotlin.fir.plugin.createMemberFunction +import org.jetbrains.kotlin.fir.symbols.impl.FirClassSymbol +import org.jetbrains.kotlin.fir.symbols.impl.FirNamedFunctionSymbol +import org.jetbrains.kotlin.name.CallableId +import org.jetbrains.kotlin.name.Name + +/** + * Name of methods for which we will generate default implementations if the users have not + * defined their own. + */ +private val realmObjectDefaultMethods = setOf( + Names.REALM_OBJECT_TO_STRING_METHOD, + Names.REALM_OBJECT_EQUALS, + Names.REALM_OBJECT_HASH_CODE, +) + +/** + * Fir extension that adds `toString`, `equals` and `hashCode` to RealmObject-classes. + */ +class ObjectExtension(session: FirSession) : FirDeclarationGenerationExtension(session) { + override fun getCallableNamesForClass( + classSymbol: FirClassSymbol<*>, + context: MemberGenerationContext + ): Set { + return if (classSymbol.isBaseRealmObject) { + val methodNames: List = classSymbol.declarationSymbols.filterIsInstance().map { it.name } + realmObjectDefaultMethods.filter { !methodNames.contains(it) }.toSet() + } else { + emptySet() + } + } + + override fun generateFunctions( + callableId: CallableId, + context: MemberGenerationContext? + ): List { + val owner = context?.owner ?: return emptyList() + return when (callableId.callableName) { + Names.REALM_OBJECT_TO_STRING_METHOD -> + listOf( + createMemberFunction( + owner, + RealmPluginGeneratorKey, + callableId.callableName, + session.builtinTypes.stringType.type, + ) { + modality = Modality.OPEN + }.symbol + ) + Names.REALM_OBJECT_EQUALS -> + listOf( + createMemberFunction( + owner, + RealmPluginGeneratorKey, + callableId.callableName, + session.builtinTypes.booleanType.type, + ) { + modality = Modality.OPEN + valueParameter(Name.identifier("other"), session.builtinTypes.nullableAnyType.type) + }.symbol + ) + Names.REALM_OBJECT_HASH_CODE -> + listOf( + createMemberFunction( + owner, + RealmPluginGeneratorKey, + callableId.callableName, + session.builtinTypes.intType.type, + ) { + modality = Modality.OPEN + }.symbol + ) + else -> emptyList() + } + } +} diff --git a/packages/plugin-compiler/src/main/kotlin/io/realm/kotlin/compiler/fir/model/RealmModelRegistrar.kt b/packages/plugin-compiler/src/main/kotlin/io/realm/kotlin/compiler/fir/model/RealmModelRegistrar.kt new file mode 100644 index 0000000000..b0897ddaf6 --- /dev/null +++ b/packages/plugin-compiler/src/main/kotlin/io/realm/kotlin/compiler/fir/model/RealmModelRegistrar.kt @@ -0,0 +1,38 @@ +/* + * Copyright 2023 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.fir.model + +import org.jetbrains.kotlin.fir.extensions.FirExtensionRegistrar + +/** + * Fir extension registrar that adds extensions to modify the Realm object API. + * + * This consists of: + * - A [CompanionExtension] that adds a (or updates existing) companion object to + * RealmObject-classes and modifies it to implement the [RealmObjectCompanion] interface and its + * methods. + * - An [ObjectExtension] that adds `toString`, `equals` and `hashCode` methods to + * RealmObject-classes. + * + * All API modifications should be tagged with the [RealmPluginGeneratorKey] to make it recognizable + * in other compiler plugin phases. + */ +class RealmModelRegistrar : FirExtensionRegistrar() { + override fun ExtensionRegistrarContext.configurePlugin() { + +::CompanionExtension + +::ObjectExtension + } +} diff --git a/packages/test-base/build.gradle.kts b/packages/test-base/build.gradle.kts index 8976c4f7c8..e56161f7d7 100644 --- a/packages/test-base/build.gradle.kts +++ b/packages/test-base/build.gradle.kts @@ -271,6 +271,12 @@ kotlin { } } +tasks.withType().all { + kotlinOptions { + freeCompilerArgs += listOf("-opt-in=kotlinx.cinterop.ExperimentalForeignApi") + } +} + // Rules for getting Kotlin Native resource test files in place for locating it with the `assetFile` // configuration. For JVM platforms the files are placed in // `src/jvmTest/resources`(non-Android JVM) and `src/androidTest/assets` (Android). diff --git a/packages/test-base/src/commonMain/kotlin/io/realm/kotlin/entities/SerializableSample.kt b/packages/test-base/src/commonMain/kotlin/io/realm/kotlin/entities/SerializableSample.kt index 9e6b939bf7..6a15ef617d 100644 --- a/packages/test-base/src/commonMain/kotlin/io/realm/kotlin/entities/SerializableSample.kt +++ b/packages/test-base/src/commonMain/kotlin/io/realm/kotlin/entities/SerializableSample.kt @@ -351,4 +351,9 @@ class SerializableSample : RealmObject { @Serializable class SerializableEmbeddedObject : EmbeddedRealmObject { var name: String = "hello world" + + // Supplying custom companion object to work around that multiple K2 FIR extension clashes if + // they both generate a Companion. + // https://youtrack.jetbrains.com/issue/KT-62194/K2-Two-compiler-plugins-interference-in-generated-companion-object + companion object } diff --git a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmSchemaTests.kt b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmSchemaTests.kt index 7f5debbd7e..5bb4e0f5a6 100644 --- a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmSchemaTests.kt +++ b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmSchemaTests.kt @@ -211,7 +211,7 @@ class RealmSchemaTests { @Suppress("NestedBlockDepth") fun schema_optionCoverage() { // Property options - @Suppress("invisible_member") + @Suppress("invisible_member", "invisible_reference") val propertyTypeMap = RealmPropertyType.subTypes.map { it to RealmStorageType.values().toMutableSet() } .toMap().toMutableMap() diff --git a/packages/test-base/src/nativeDarwinTest/kotlin/io/realm/kotlin/test/darwin/CoroutineTests.kt b/packages/test-base/src/nativeDarwinTest/kotlin/io/realm/kotlin/test/darwin/CoroutineTests.kt index 2b7dfd654b..3822f03a71 100644 --- a/packages/test-base/src/nativeDarwinTest/kotlin/io/realm/kotlin/test/darwin/CoroutineTests.kt +++ b/packages/test-base/src/nativeDarwinTest/kotlin/io/realm/kotlin/test/darwin/CoroutineTests.kt @@ -67,7 +67,7 @@ class CoroutineTests { @Test @Ignore fun dispatchQueueScheduler() { - val queue = dispatch_get_global_queue(NSNumber(DISPATCH_QUEUE_PRIORITY_BACKGROUND).integerValue, 0) + val queue = dispatch_get_global_queue(NSNumber(DISPATCH_QUEUE_PRIORITY_BACKGROUND).integerValue, 0UL) val dispatcher = NsQueueDispatcher(queue) CoroutineScope(dispatcher).async { printlntid("async") diff --git a/packages/test-base/src/nativeDarwinTest/kotlin/io/realm/kotlin/test/darwin/MemoryTests.kt b/packages/test-base/src/nativeDarwinTest/kotlin/io/realm/kotlin/test/darwin/MemoryTests.kt index ff29ef5503..f6493591c6 100644 --- a/packages/test-base/src/nativeDarwinTest/kotlin/io/realm/kotlin/test/darwin/MemoryTests.kt +++ b/packages/test-base/src/nativeDarwinTest/kotlin/io/realm/kotlin/test/darwin/MemoryTests.kt @@ -19,6 +19,7 @@ package io.realm.kotlin.test.darwin import io.realm.kotlin.Realm import io.realm.kotlin.RealmConfiguration import io.realm.kotlin.entities.Sample +import io.realm.kotlin.ext.query import io.realm.kotlin.test.platform.PlatformUtils.createTempDir import io.realm.kotlin.test.platform.PlatformUtils.deleteTempDir import io.realm.kotlin.test.platform.PlatformUtils.triggerGC @@ -33,6 +34,7 @@ import platform.posix.popen import kotlin.math.roundToInt import kotlin.test.AfterTest import kotlin.test.BeforeTest +import kotlin.test.Ignore import kotlin.test.Test import kotlin.test.assertEquals @@ -55,6 +57,63 @@ class MemoryTests { deleteTempDir(tmpDir) } + // TODO Only run on macOS, filter using https://developer.apple.com/documentation/foundation/nsprocessinfo/3608556-iosapponmac when upgrading to XCode 12 + @Test + @Ignore // Investigate https://github.com/realm/realm-kotlin/issues/327 + fun garbageCollectorShouldFreeNativeResources() { + @OptIn(ExperimentalStdlibApi::class) + println("NEW_MEMORY_MODEL: " + isExperimentalMM()) + + val referenceHolder = mutableListOf(); + { + val realm = openRealmFromTmpDir() + // TODO use Realm.delete once this is implemented + realm.writeBlocking { + delete(query()) + } + + // allocating a 1 MB string + val oneMBstring = StringBuilder("").apply { + for (i in 1..4096) { + // 128 length (256 bytes) + append("v7TPOZtm50q8kMBoKiKRaD2JhXgjM6OUNzHojXuFXvxdtwtN9fCVIW4njdwVdZ9aChvXCtW4nzUYeYWbI6wuSspbyjvACtMtjQTtOoe12ZEPZPII6PAFTfbrQQxc3ymJ") + } + }.toString() + + // inserting ~ 100MB of data + val elements: List = + realm.writeBlocking { + IntRange(1, 100).map { + copyToRealm(Sample()).apply { + stringField = oneMBstring + } + } + } + referenceHolder.addAll(elements) + }() + assertEquals( + "99.0M", + runSystemCommand(amountOfMemoryMappedInProcessCMD), + "We should have at least 99 MB allocated as mmap" + ) + // After releasing all the 'realm_object_create' reference the Realm should be closed and the + // no memory mapped file is allocated in the process + referenceHolder.clear() + triggerGC() + + platform.posix.sleep(1U * 5U) // give chance to the Collector Thread to process references + + // We should find a way to just meassure the increase over these tests. Referencing + // NSProcessInfo.Companion.processInfo().operatingSystemVersionString + // as done in Darwin SystemUtils.kt can also cause allocations. Thus, just lazy evaluating + // those system constants for now to avoid affecting the tests. + assertEquals( + "", + runSystemCommand(amountOfMemoryMappedInProcessCMD), + "Freeing the references should close the Realm so no memory mapped allocation should be present" + ) + } + // TODO Only run on macOS, filter using https://developer.apple.com/documentation/foundation/nsprocessinfo/3608556-iosapponmac when upgrading to XCode 12 @Test fun closeShouldFreeMemory() { @@ -67,7 +126,7 @@ class MemoryTests { // as done in Darwin SystemUtils.kt and initialized lazily, so do a full realm-lifecycle // to only measure increases over the actual test // - Ensure that we clean up any released memory to get a nice baseline - platform.posix.sleep(1 * 5) // give chance to the Collector Thread to process out of scope references + platform.posix.sleep((1 * 5).toUInt()) // give chance to the Collector Thread to process out of scope references triggerGC() // - Record the baseline val initialAllocation = parseSizeString(runSystemCommand(amountOfMemoryMappedInProcessCMD)) @@ -98,7 +157,7 @@ class MemoryTests { }() triggerGC() - platform.posix.sleep(1 * 5) // give chance to the Collector Thread to process out of scope references + platform.posix.sleep(1U * 5U) // give chance to the Collector Thread to process out of scope references val allocation = parseSizeString(runSystemCommand(amountOfMemoryMappedInProcessCMD)) assertEquals(initialAllocation, allocation, "mmap allocation exceeds expectations: initial=$initialAllocation current=$allocation") diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/FunctionsTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/FunctionsTests.kt index 354bc859ac..cfd87265a1 100644 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/FunctionsTests.kt +++ b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/FunctionsTests.kt @@ -139,6 +139,11 @@ val BSON_DOCUMENT_VALUE = BsonDocument( class SerializablePerson : RealmObject { var firstName: String = "FIRST NAME" var lastName: String = "LAST NAME" + + // Supplying custom companion object to work around that multiple K2 FIR extension clashes if + // they both generate a Companion. + // https://youtrack.jetbrains.com/issue/KT-62194/K2-Two-compiler-plugins-interference-in-generated-companion-object + companion object } class FunctionsTests {