Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

K2 support #1525

Merged
merged 20 commits into from
Nov 14, 2023
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe add the restriction about companion classes and Serialization or maybe reference some other place (either issue or documentation about it)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I created #1567 to track it. This should enable users to find and workaround the issue. Do we also need it as known issues?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably not, my guess is that more people will find it if we just pin #1567.


### 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.27.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason we are bumping the minimum?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Earlier versions of R8 does not know how to read Kotlin 1.9 metadata. This will cause the consumer project to fail because it will not properly honor the configuration when obfuscating out library. https://developer.android.com/build/kotlin-support


### 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
rorbech marked this conversation as resolved.
Show resolved Hide resolved
}
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)
cmelchior marked this conversation as resolved.
Show resolved Hide resolved

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,
0.toULong(),
rorbech marked this conversation as resolved.
Show resolved Hide resolved
capacityCount.ptr
)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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, 1.toULong())
rorbech marked this conversation as resolved.
Show resolved Hide resolved

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 @@ -40,6 +40,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 @@ -160,5 +163,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(FqName("io.realm.kotlin.mongodb"), FqName("AppConfiguration.Builder"), false)
rorbech marked this conversation as resolved.
Show resolved Hide resolved
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,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 @@ -141,10 +147,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 +174,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 +195,30 @@ val ClassDescriptor.isEmbeddedRealmObject: Boolean
val ClassDescriptor.isBaseRealmObject: Boolean
get() = this.hasInterfacePsi(realmObjectPsiNames + embeddedRealmObjectPsiNames + asymmetricRealmObjectPsiNames) && !this.hasInterfacePsi(realmJavaObjectPsiNames)

val realmObjectTypes = setOf(Name.identifier("RealmObject"), Name.identifier("EmbeddedRealmObject"), Name.identifier("AsymmetricRealmObject"))
rorbech marked this conversation as resolved.
Show resolved Hide resolved
val realmObjectClassIds = realmObjectTypes.map { name -> ClassId(FqName("io.realm.kotlin.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?.run { debugName == "CONSTRUCTOR_CALLEE" } ?: false)
rorbech marked this conversation as resolved.
Show resolved Hide resolved
}
// 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
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

/**
Expand Down Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
@@ -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"
}
}
Loading