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

Compiler/linker error on iOS with composable lambdas. #2694

Closed
mipastgt opened this issue Feb 3, 2023 · 14 comments
Closed

Compiler/linker error on iOS with composable lambdas. #2694

mipastgt opened this issue Feb 3, 2023 · 14 comments
Assignees
Labels
compiler Compiler plugin related

Comments

@mipastgt
Copy link

mipastgt commented Feb 3, 2023

I am currently trying to get Bonsai Core sources working in a multiplatform project with
iOS which is currently not supported by Bonsai.

This works out of the box for desktop and Android (both JVM) but compilation/linking fails
for iOS. It all boils down to the fact that the following combination of classes (stripped
down from the originals) does not compile/link on iOS (and maybe other native targets too).

import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable
import androidx.compose.ui.graphics.painter.Painter

typealias NodeComponentX<T> = @Composable BonsaiScopeX<T>.(NodeX<T>) -> Unit

sealed interface NodeX<T> {
    val iconComponent: NodeComponentX<T>
}

typealias NodeIconX<T> = @Composable (NodeX<T>) -> Painter?

@Immutable
data class BonsaiScopeX<T> internal constructor(
    internal val style: BonsaiStyleX<T>,
)

data class BonsaiStyleX<T>(
    val nodeCollapsedIcon: NodeIconX<T> = { null },
)

The build terminates with:

> Task :shared:linkPodDebugFrameworkIosX64
e: There are still 2 unbound symbols after generation of IR module <shared>:
Unbound public symbol IrConstructorPublicSymbolImpl: /BonsaiStyleX.<init>|-8323399005056659232[0]
Unbound public symbol IrSimpleFunctionPublicSymbolImpl: /BonsaiStyleX.copy|8877863784634607273[0]

This could happen if there are two libraries, where one library was compiled against the different version of the other library than the one currently used in the project. Please check that the project configuration is correct and has consistent versions of dependencies.

I made the following observations:

  1. This can be made to compile/link when I remove the two @Composable annotations
    (but then the code does not work anymore).
  2. It somehow also seems to depend on the project structure because in an older project,
    with a different structure, this does compile/link. I have verified the above error by just
    throwing the above code contained in a single file into the shared commonMain of
    https://github.com/JetBrains/compose-jb/tree/master/experimental/examples/falling-balls-mpp

Do you have an idea how to fix that or a usable workaround?

I tested this on macOS 12.6.3 Monterey, Kotlin 1.8.0, Compose 1.3.0

References:
https://github.com/adrielcafe/bonsai
adrielcafe/bonsai#11

@eymar
Copy link
Member

eymar commented Feb 3, 2023

I can only confirm these issues exist. I noticed them myself recently. We plan to fix them. Unfortunately, I don't know a workaround, probably there is no WA.

It somehow also seems to depend on the project structure because in an older project,
with a different structure, this does compile/link.

What was the structure that used to work for you?

@eymar eymar added the compiler Compiler plugin related label Feb 3, 2023
@eymar eymar self-assigned this Feb 3, 2023
@mipastgt
Copy link
Author

mipastgt commented Feb 3, 2023

I just verified that this problem is indeed related to this issue #2346

The workaround is to make every composable function internal. But that is a bit of a pain for the above code because it also uses composable lambdas as members in data structures and so all of these have to be made internal too and all functions which expose such data. So for me this can only be a temporary workaround.

The structure which still seems to work is the old structure of the expermental examples before they were refactored recently.

@eymar
Copy link
Member

eymar commented Feb 3, 2023

Oh, I didn't realise it's that same issue.

Since kotlin 1.8.0 you may try to add this annotation to your declarations instead of marking them internal
https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.native/-hidden-from-obj-c/

Anyway, it's not a convenient solution and we're working on adding that annotation automatically

@mipastgt
Copy link
Author

mipastgt commented Feb 3, 2023

That does not seem to be a valid workaround for me because, as far as I can see, this annotation cannot be applied to typealiases. (At least that's what the compiler is telling me.)

@eymar
Copy link
Member

eymar commented Feb 3, 2023

But is it really necessary to add it to the typealias? What if you add to the functions only and properties which use those typealiases?

I'll check it myself later.

@mipastgt
Copy link
Author

mipastgt commented Feb 3, 2023

I tried adding @HiddenFromObjC everywhere where it was applicable (and @file:OptIn(ExperimentalObjCRefinement::class)) but I then got the same error as shown above.

@eymar
Copy link
Member

eymar commented Feb 3, 2023

I haven't tried it yet.
But just an idea that you could also try:
We have a lot of public composable functions in the compose-ui, compose-foundation modules and we didn't add the annotation to them and they don't cause this issue.

What if the main.kt file moved to another module that depends on all your modules?
The idea is that "main" module won't have any declarations except the app initialization.

@eymar
Copy link
Member

eymar commented Feb 6, 2023

@mipastgt while we work on a solution that would eliminate the need in any workarounds (it may take some time, because changes in kotlin involved), here is our imageviewer example #2705 where we removed internal visibility.

That PR implements what I mentioned earlier:

The idea is that "main" module won't have any declarations except the app initialization.

@mipastgt
Copy link
Author

mipastgt commented Feb 6, 2023

At the moment I cannot try it because my iOS setup is currently completely broken and I have to get some other work done before I can fix that.

Just to clarify this. The issue is only relevant for my own project sources, right? If I pull in composable functions from an external library this is not an issue?

@eymar
Copy link
Member

eymar commented Feb 6, 2023

Yes, the composables from an external library won't cause an issue

@AlexGladkov
Copy link
Collaborator

The same error but not for composables function

Showing Recent Issues
Unbound public symbol IrConstructorPublicSymbolImpl: ru.alexgladkov.odyssey.compose.navigation.bottom_bar_navigation/TabInfo.|150164194066322410[0]

Unbound public symbol IrSimpleFunctionPublicSymbolImpl: ru.alexgladkov.odyssey.compose.navigation.bottom_bar_navigation/TabsNavModel.navConfiguration.|3814956042047583694[0]

Unbound public symbol IrSimpleFunctionPublicSymbolImpl: ru.alexgladkov.odyssey.compose.navigation.bottom_bar_navigation/TabItem.configuration.|1523816184942411441[0]

Unbound public symbol IrSimpleFunctionPublicSymbolImpl: ru.alexgladkov.odyssey.compose.navigation.bottom_bar_navigation/TabInfo.copy|7688116277759904276[0]

Error with some init, copy functions of public data classes. IDK how to fix this, because this classes must be public

@jbruchanov
Copy link

Evening guys,

I have same issue, is there any progress or official ticket for the issue ?

I've found that basically hiding anything what is @composable and somehow living inside OOP helps.
@HiddenFromObjC annotation helps, though as it has quite limited targeting it's not exactly strong workaround tool.

I'm wondering, is there any particular reason to expose anything @Composable to ios world anyway ?
I doubt there is, if at all, any usage for it.

few code "dance" I use to workaround the issue

fun ContentRef(content: @Composable () -> Unit) = ContentRef(content, Unit)

class ContentRef internal constructor(
    internal val content: @Composable () -> Unit, 
    unit: Unit
) {
    
    @Composable
    internal fun render() = content()

    @HiddenFromObjC
    inline fun whatever(comp : @Composable () -> Unit) {
        //...
    }
}

@Composable
public fun render(contentRef: ContentRef) {
    contentRef.content()
}

@eymar
Copy link
Member

eymar commented Apr 12, 2023

The fix is available with Compose Multiplatform 1.4.0 when used with kotlin 1.8.20

@okushnikov
Copy link
Collaborator

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

@JetBrains JetBrains locked and limited conversation to collaborators Dec 18, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
compiler Compiler plugin related
Projects
None yet
Development

No branches or pull requests

5 participants