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
15.26k stars 1.11k forks source link

RuntimeError: illegal cast for wasmJs with SparseArrayCompat.internalGet #4830

Closed eygraber closed 3 weeks ago

eygraber commented 1 month ago

Describe the bug

I've been experimenting with porting navigation with types here, and I just tried running in a wasmJs app, and I get this error:

illegal cast
RuntimeError: illegal cast
    at <com.eygraber:wasmJsApp>.androidx.collection.commonGet (http://localhost:8080/nav-sample-wasm.wasm:wasm-function[20938]:0x3afa25)
    at <com.eygraber:wasmJsApp>.androidx.collection.SparseArrayCompat.get (http://localhost:8080/nav-sample-wasm.wasm:wasm-function[21016]:0x3b1d4b)
    at <com.eygraber:wasmJsApp>.androidx.navigation.NavGraph.findNode (http://localhost:8080/nav-sample-wasm.wasm:wasm-function[41678]:0x55ca60)
    at <com.eygraber:wasmJsApp>.androidx.navigation.NavGraph.findNode (http://localhost:8080/nav-sample-wasm.wasm:wasm-function[41675]:0x55c9b7)
    at <com.eygraber:wasmJsApp>.androidx.navigation.NavGraph.setStartDestination (http://localhost:8080/nav-sample-wasm.wasm:wasm-function[41689]:0x55cf09)
    at <com.eygraber:wasmJsApp>.androidx.navigation.NavGraph.setStartDestination (http://localhost:8080/nav-sample-wasm.wasm:wasm-function[41688]:0x55ced8)
    at <com.eygraber:wasmJsApp>.androidx.navigation.NavGraphBuilder.build (http://localhost:8080/nav-sample-wasm.wasm:wasm-function[41712]:0x55d734)
    at <com.eygraber:wasmJsApp>.androidx.navigation.compose.navigation (http://localhost:8080/nav-sample-wasm.wasm:wasm-function[45157]:0x5a2b8d)

My thinking was that wasmJs doesn't like the cast to T since the actual type is E:

@Suppress("NOTHING_TO_INLINE")
private inline fun <E, T : E?> SparseArrayCompat<E>.internalGet(key: Int, defaultValue: T): T {
    val i = binarySearch(keys, size, key)
    return if (i < 0 || values[i] === DELETED) {
        defaultValue
    } else {
        @Suppress("UNCHECKED_CAST")
        values[i] as T
    }
}

Changing the cast (and return type) to E fixes the problem:

@Suppress("NOTHING_TO_INLINE")
private inline fun <E, T : E?> SparseArrayCompat<E>.internalGet(key: Int, defaultValue: T): E {
    val i = binarySearch(keys, size, key)
    return if (i < 0 || values[i] === DELETED) {
        @Suppress("UNCHECKED_CAST")
        defaultValue as E
    } else {
        @Suppress("UNCHECKED_CAST")
        values[i] as E
    }
}

I don't think that's the actual solution though, because I think that will cause cast issues if the default is being used and it is null (although I'm not 100% sure).

I haven't tested with js. jvm works fine without changing the cast and return type.

Affected platforms

Versions

To Reproduce Steps to reproduce the behavior:

  1. Clone my nav-types branch
  2. Deploy to maven local with ./gradlew :mpp:publishComposeJbToMavenLocal -Pcompose.platforms=js,jvm,androidRelease,androidDebug,wasmJs -Pjetbrains.publication.version.COMPOSE=1.6.10-rc02 -Pjetbrains.publication.version.LIFECYCLE=2.8.0-rc01
  3. Clone my sample app branch
  4. Run with ./gradlew :samples:nav:wasmJsApp:wasmJsBrowserRun
  5. See error
MatkovIvan commented 4 weeks ago

Reasigning to web folks since it's not really related to navigation - it's about usage of SparseArrayCompat in forked code

MatkovIvan commented 3 weeks ago

Fixed in https://github.com/JetBrains/compose-multiplatform-core/commit/38b8a078a73854eb0a7ba64ead4e4310dad0c5bd