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

1.0.0-ALPHA-4 doesn't expose types properly for generic abstract classes #90

Open
tfcporciuncula opened this issue Jan 24, 2023 · 5 comments
Assignees

Comments

@tfcporciuncula
Copy link

I have a minimum reproducible example here: https://github.com/tfcporciuncula/native-coroutines-kotlin-18

Let's say we have something like this:

abstract class AbstractClass<T>(private val initial: T) {
  val channel = Channel<T>(Channel.CONFLATED)
  @NativeCoroutines val testFlow: Flow<T> = channel.receiveAsFlow()

  val _testStateFlow = MutableStateFlow(initial)
  @NativeCoroutines val testStateFlow: StateFlow<T> = _testStateFlow.asStateFlow()

  @NativeCoroutines suspend fun testSuspend(): T {
    delay(1000)
    return initial
  }
}

What I get from KMP-NativeCoroutines (Swift 5 Interface counterpart) is this:

extension AbstractClass {

    open func testSuspend() -> (@escaping (Any?, KotlinUnit) -> KotlinUnit, @escaping (Error, KotlinUnit) -> KotlinUnit, @escaping (Error, KotlinUnit) -> KotlinUnit) -> () -> KotlinUnit

    open var testFlow: (@escaping (Any?, @escaping () -> KotlinUnit, KotlinUnit) -> KotlinUnit, @escaping (Error?, KotlinUnit) -> KotlinUnit, @escaping (Error, KotlinUnit) -> KotlinUnit) -> () -> KotlinUnit { get }

    open var testStateFlow: (@escaping (Any?, @escaping () -> KotlinUnit, KotlinUnit) -> KotlinUnit, @escaping (Error?, KotlinUnit) -> KotlinUnit, @escaping (Error, KotlinUnit) -> KotlinUnit) -> () -> KotlinUnit { get }

    open var testStateFlowValue: Any? { get }
}

The generic T becomes Any?. I have a branch on Kotlin 1.7.20 and KMP-NativeCoroutines 0.13.1 so we can easily compare what we would get before, which is this:

open class AbstractClass<T> : KotlinBase where T : AnyObject {

    public init(initial: T?)

    
    /**
     * @note This method converts instances of CancellationException to errors.
     * Other uncaught Kotlin exceptions are fatal.
    */
    open func testSuspend(completionHandler: @escaping (T?, Error?) -> Void)

    /**
     * @note This method converts instances of CancellationException to errors.
     * Other uncaught Kotlin exceptions are fatal.
    */
    open func testSuspend() async throws -> T?

    open func testSuspendNative() -> (@escaping (T?, KotlinUnit) -> KotlinUnit, @escaping (Error, KotlinUnit) -> KotlinUnit) -> () -> KotlinUnit

    open var _testStateFlow: Kotlinx_coroutines_coreMutableStateFlow { get }

    open var _testStateFlowNative: (@escaping (T?, KotlinUnit) -> KotlinUnit, @escaping (Error?, KotlinUnit) -> KotlinUnit) -> () -> KotlinUnit { get }

    open var _testStateFlowNativeValue: T? { get }

    open var channel: Kotlinx_coroutines_coreChannel { get }

    open var testFlow: Kotlinx_coroutines_coreFlow { get }

    open var testFlowNative: (@escaping (T?, KotlinUnit) -> KotlinUnit, @escaping (Error?, KotlinUnit) -> KotlinUnit) -> () -> KotlinUnit { get }

    open var testStateFlow: Kotlinx_coroutines_coreStateFlow { get }

    open var testStateFlowNative: (@escaping (T?, KotlinUnit) -> KotlinUnit, @escaping (Error?, KotlinUnit) -> KotlinUnit) -> () -> KotlinUnit { get }

    open var testStateFlowNativeValue: T? { get }
}

Here we can see T is there so we didn't lose anything. Is there anything I might be missing on my end or is that potentially a regression on the latest version?

@KwabenBerko
Copy link

Created a similar ticket in the KMMViewModel project: rickclephas/KMP-ObservableViewModel#15

@rickclephas
Copy link
Owner

@tfcporciuncula this is an unfortunate side effect of the new code generation approach (with KSP).
v1.0 generated extension properties/functions. Which in case of a generic class results in a generic function/property which isn't supported in Objective-C.

I am going to take a look at this to see if there is a way we can improve this 😄.

@rickclephas rickclephas self-assigned this Jan 24, 2023
@harry248
Copy link

@tfcporciuncula Did you find a workaround? @rickclephas What do you recommend to tackle this issue?

@tfcporciuncula
Copy link
Author

Nope, I don't think there's a workaround. We didn't have a lot of cases where we were exposing flows, so we just minimized them even more and handled them ourselves without a library. You could also consider SKIE as I believe it doesn't have this limitation, but I haven't verified it myself.

@rickclephas
Copy link
Owner

@harry248 unfortunately there isn't really a proper solution. However there are some ways to workaround this limitation:

  • use @NativeCoroutinesRefined and refine the declaration in a Swift extension with a forced cast
  • annotate the declarations in concrete subclasses instead of the generic super class
  • creating the native declarations manually in your appleMain source-set through expect/actual

and as @tfcporciuncula mentioned SKIE doesn't have this limitation since it doesn't use extensions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants