Skip to content

Commit

Permalink
K2 support (#1525)
Browse files Browse the repository at this point in the history
  • Loading branch information
rorbech authored Nov 14, 2023
1 parent be75e66 commit ccd8ae9
Show file tree
Hide file tree
Showing 28 changed files with 455 additions and 31 deletions.
5 changes: 3 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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.
Expand All @@ -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.
Expand Down
2 changes: 2 additions & 0 deletions Jenkinsfile
Original file line number Diff line number Diff line change
Expand Up @@ -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') {
Expand All @@ -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') {
Expand Down
8 changes: 4 additions & 4 deletions buildSrc/src/main/kotlin/Config.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion examples/kmm-sample/shared/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
1 change: 1 addition & 0 deletions packages/cinterop/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,7 @@ kotlin {
compilations.all {
kotlinOptions {
freeCompilerArgs += listOf("-opt-in=kotlin.ExperimentalUnsignedTypes")
freeCompilerArgs += listOf("-opt-in=kotlinx.cinterop.ExperimentalForeignApi")
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1985,7 +1985,7 @@ actual object RealmInterop {
realm_wrapper.realm_app_get_all_users(
app.cptr(),
null,
0,
0UL,
capacityCount.ptr
)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<CPointerVarOf<CPointer<realm_property_info_t>>> =
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(
Expand All @@ -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_t>? = realm_open(config)
assertEquals(1U, realm_get_num_classes(realm))
Expand Down
7 changes: 6 additions & 1 deletion packages/library-base/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>().configureEach {
kotlinOptions {
freeCompilerArgs = listOf("-module-name", "io.realm.kotlin.library")
freeCompilerArgs += listOf("-module-name", "io.realm.kotlin.library")
}
}
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinNativeCompile>().all {
kotlinOptions {
freeCompilerArgs += listOf("-opt-in=kotlinx.cinterop.ExperimentalForeignApi")
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<Context> {
internal class RealmSyncInitializer : Initializer<Context> {

companion object {
@Suppress("DEPRECATION") // Should only be called below API 21
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ internal class MutableSubscriptionSetImpl<T : BaseRealm>(
return result
}

@Suppress("invisible_member")
@Suppress("invisible_member", "invisible_reference")
override fun <T : RealmObject> removeAll(type: KClass<T>): Boolean {
var result = false
val objectType = io.realm.kotlin.internal.platform.realmObjectCompanionOrThrow(type).`io_realm_kotlin_className`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 <T : RealmObject> asQuery(type: KClass<T>): RealmQuery<T> {
// TODO Check for invalid combinations of Realm and type once we properly support
// DynamicRealm
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<ConnectionStateChange> = realm.scopedFlow {
callbackFlow {
val token: AtomicRef<Cancellable> = kotlinx.atomicfu.atomic(NO_OP_NOTIFICATION_TOKEN)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand All @@ -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("<set-?>")

Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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)
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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<String>): 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<String>): 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
Expand All @@ -169,6 +179,10 @@ inline fun ClassDescriptor.hasInterfacePsi(interfaces: Set<String>): Boolean {

return hasRealmObjectAsSuperType
}
inline fun ClassDescriptor.hasInterfacePsi(interfaces: Set<String>): 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,
Expand All @@ -186,6 +200,34 @@ val ClassDescriptor.isEmbeddedRealmObject: Boolean
val ClassDescriptor.isBaseRealmObject: Boolean
get() = this.hasInterfacePsi(realmObjectPsiNames + embeddedRealmObjectPsiNames + asymmetricRealmObjectPsiNames) && !this.hasInterfacePsi(realmJavaObjectPsiNames)

val realmObjectTypes: Set<Name> = 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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -62,7 +62,7 @@ class RealmModelSyntheticMethodsExtension : SyntheticResolveExtension {
)
)
}
"equals" -> {
Names.REALM_OBJECT_EQUALS -> {
result.add(
createMethod(
classDescriptor = thisDescriptor,
Expand All @@ -72,7 +72,7 @@ class RealmModelSyntheticMethodsExtension : SyntheticResolveExtension {
)
)
}
"hashCode" -> {
Names.REALM_OBJECT_HASH_CODE -> {
result.add(
createMethod(
classDescriptor = thisDescriptor,
Expand Down
Loading

0 comments on commit ccd8ae9

Please sign in to comment.