JetBrains / compose-multiplatform

Compose Multiplatform, a modern UI framework for Kotlin that makes building performant and beautiful user interfaces easy and enjoyable.
https://jetbrains.com/lp/compose-multiplatform
Apache License 2.0
16.22k stars 1.18k forks source link

kotlin-js fails to compile when using an anonymous object in a remember block inside a composable #2615

Closed pablichjenkov closed 1 month ago

pablichjenkov commented 1 year ago

Using code similar to the one shown below will cause the kotlin compiler the fail when targeting JS(IR).

@Composable
fun BackPressHandler(onBackPressed: () -> Unit) {
    // Safely update the current `onBack` lambda when a new one is provided
    val currentOnBackPressed by rememberUpdatedState(onBackPressed)

    // Remember in Composition a back callback that calls the `onBackPressed` lambda
    val backCallback = remember {
        object : BackPressedCallback() { // this line fails compilation
            override fun onBackPressed() {
                currentOnBackPressed()
            }
        }
    }

  ...

}

Exception:

Task :compileDevelopmentExecutableKotlinJs FAILED e: java.lang.IllegalStateException: Not found Idx for com.pablichj.incubator.uistate3.node/BackPressHandler$composable|8444751713610266539[0] at org.jetbrains.kotlin.backend.common.serialization.IrFileDeserializer.loadTopLevelDeclarationProto(IrFileDeserializer.kt) at org.jetbrains.kotlin.backend.common.serialization.IrFileDeserializer.deserializeDeclaration(IrFileDeserializer.kt:40) at org.jetbrains.kotlin.backend.common.serialization.FileDeserializationState.deserializeAllFileReachableTopLevel(IrFileDeserializer.kt:135) at org.jetbrains.kotlin.backend.common.serialization.ModuleDeserializationState.deserializeReachableDeclarations(BasicIrModuleDeserializer.kt:178) at org.jetbrains.kotlin.backend.common.serialization.BasicIrModuleDeserializer.deserializeReachableDeclarations(BasicIrModuleDeserializer.kt:145) at org.jetbrains.kotlin.backend.common.serialization.KotlinIrLinker.deserializeAllReachableTopLevels(KotlinIrLinker.kt:111) at org.jetbrains.kotlin.backend.common.serialization.KotlinIrLinker.findDeserializedDeclarationForSymbol(KotlinIrLinker.kt:129) at org.jetbrains.kotlin.backend.common.serialization.KotlinIrLinker.deserializeOrResolveDeclaration(KotlinIrLinker.kt:169) at org.jetbrains.kotlin.backend.common.serialization.KotlinIrLinker.getDeclaration(KotlinIrLinker.kt:158) at org.jetbrains.kotlin.ir.util.ExternalDependenciesGeneratorKt.getDeclaration(ExternalDependenciesGenerator.kt:57) at org.jetbrains.kotlin.ir.util.ExternalDependenciesGenerator.generateUnboundSymbolsAsDependencies(ExternalDependenciesGenerator.kt:44) at org.jetbrains.kotlin.ir.backend.js.KlibKt.getIrModuleInfoForKlib(klib.kt:391) at org.jetbrains.kotlin.ir.backend.js.KlibKt.loadIr(klib.kt:334) at org.jetbrains.kotlin.ir.backend.js.CompilerKt.compile(compiler.kt:66) at org.jetbrains.kotlin.ir.backend.js.CompilerKt.compile$default(compiler.kt:49) at org.jetbrains.kotlin.cli.js.K2JsIrCompiler.doExecute(K2JsIrCompiler.kt:392) at org.jetbrains.kotlin.cli.js.K2JSCompiler.doExecute(K2JSCompiler.java:183) at org.jetbrains.kotlin.cli.js.K2JSCompiler.doExecute(K2JSCompiler.java:72) at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:94) at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:43) at org.jetbrains.kotlin.cli.common.CLITool.exec(CLITool.kt:101) at org.jetbrains.kotlin.daemon.CompileServiceImpl.compile(CompileServiceImpl.kt:1642) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.base/java.lang.reflect.Method.invoke(Method.java:568) at java.rmi/sun.rmi.server.UnicastServerRef.dispatch(UnicastServerRef.java:360) at java.rmi/sun.rmi.transport.Transport$1.run(Transport.java:200) at java.rmi/sun.rmi.transport.Transport$1.run(Transport.java:197) at java.base/java.security.AccessController.doPrivileged(AccessController.java:712) at java.rmi/sun.rmi.transport.Transport.serviceCall(Transport.java:196) at java.rmi/sun.rmi.transport.tcp.TCPTransport.handleMessages(TCPTransport.java:587) at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(TCPTransport.java:828) at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(TCPTransport.java:705) at java.base/java.security.AccessController.doPrivileged(AccessController.java:399) at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(TCPTransport.java:704) at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136) at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635) at java.base/java.lang.Thread.run(Thread.java:833)

w: Detected multiple Kotlin daemon sessions at build/kotlin/sessions

FAILURE: Build failed with an exception.

Can see the code in question in the following file: https://github.com/pablichjenkov/component-toolkit/blob/3816574c0b67fd4fc8dd749aab4af1b4f9362451/component-toolkit/src/commonMain/kotlin/com/pablichj/templato/component/core/backpress/BackPressHandler.kt#L23

Steps to reproduce

1- git clone git@github.com:pablichjenkov/uistate3.git 2- git checkout kotlin-js-compilation-fails 3- From the project root directory execute the following command: ./gradlew clean jsBrowserDevelopmentRun

gradle.properties:

compose.version=1.3.0-alpha01-dev827
kotlin.version=1.7.10
agp.version=7.0.4
org.gradle.jvmargs=-Xmx3g
kotlin.code.style=official
kotlin.native.cacheKind=none
kotlin.native.useEmbeddableCompilerJar=true
kotlin.native.enableDependencyPropagation=false
kotlin.mpp.enableGranularSourceSetsMetadata=true
# Enable kotlin/native experimental memory model
kotlin.native.binary.memoryModel=experimental
compose.desktop.verbose=true
android.useAndroidX=true
kotlin.js.webpack.major.version=4
org.jetbrains.compose.experimental.jscanvas.enabled=true
org.jetbrains.compose.experimental.macos.enabled=true
org.jetbrains.compose.experimental.uikit.enabled=true
pablichjenkov commented 1 year ago

Forgot to mention, if I try a different version of compose compiler, lets say, 1.3.0-rc01 or 1.3.0-beta04-dev903, then I get the following error:

> Task :compileKotlinJs FAILED
e: Could not find "org.jetbrains.kotlinx:kotlinx-coroutines-core" 

I am using version 1.6.3 of the kotlinx-coroutines in my commonMain implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.3")

eymar commented 1 year ago

Just to add more details: even more simple code fails too:

@Composable
fun TestRememberObject() {
    val obj = remember { object {} }
}
eymar commented 1 year ago

As a very rough workaround for now, you may try to create an instance you need to remember in a separate function and call it within remember {}:

fun createObject(addYourParamsHere...): BackPressedCallback {
    return object : BackPressedCallback {
        override fun onBackPressed() {
            TODO("Not yet implemented")
        }
    }
}

@Composable
fun TestRememberObject() {
    val obj = remember { createObject() }
}
eymar commented 1 year ago

Btw, I noticed the weird behaviour: It compiles normally on a second attempt (in my project though, I haven't tested your example yet). But when I change something in the code, it fails again, but compiles on a second attempt again. Did you notice this?

pablichjenkov commented 1 year ago

Work around works! Now in regards to the weird behavior you observe, I don't see the same. In my case is pretty consistent, compiler fails all the time. I have tried with the following version combinations too:

compose.version=1.2.2 kotlin.version=1.7.20

compose.version=1.2.1 kotlin.version=1.7.20

compose.version=1.3.0-alpha01-dev827 kotlin.version=1.7.10

Same behavior all combinations above.

eymar commented 1 year ago

No good fix yet, but found another thing that helps to workaround:

val backCallback = remember<BackPressedCallback > { ... } 
// or
val backCallback: BackPressedCallback = remember { ... } 
Ninjars commented 1 year ago

Thanks for that suggestion @eymar - looks like being explicit about the type when working with anonymous objects is what was needed in my similar case.

okushnikov commented 2 months ago

Please check the following ticket on YouTrack for follow-ups to this issue. GitHub issues will be closed in the coming weeks.