touchlab / SKIE

SKIE - Swift Kotlin Interface Enhancer
https://skie.touchlab.co/
Apache License 2.0
759 stars 8 forks source link

Suspend functions are not accessible with coroutinesInterop enabled #65

Closed Cassio90 closed 8 months ago

Cassio90 commented 8 months ago

What is the problem?

open class HttpResponse<T : Any>(val response: io.ktor.client.statement.HttpResponse, val provider: BodyProvider<T>) {
    val status: Int = response.status.value
    val success: Boolean = response.status.isSuccess()
    val headers: Map<String, List<String>> = response.headers.mapEntries()
    suspend fun body(): T = provider.body(response)
    suspend fun <V : Any> typedBody(type: TypeInfo): V = provider.typedBody(response, type)

    companion object {
        private fun Headers.mapEntries(): Map<String, List<String>> {
            val result = mutableMapOf<String, List<String>>()
            entries().forEach { result[it.key] = it.value }
            return result
        }
    }
}

Our issue is for example the body() function. It's contained in the .h file just fine:

__attribute__((swift_name("HttpResponse")))
@interface TSAPIHttpResponse<T> : TSAPIBase
@property (class, readonly, getter=companion) TSAPIHttpResponseCompanion *companion __attribute__((swift_name("companion")));
@property (readonly) NSDictionary<NSString *, NSArray<NSString *> *> *headers __attribute__((swift_name("headers")));
@property (readonly) id<TSAPIBodyProvider> provider __attribute__((swift_name("provider")));
@property (readonly) TSAPIKtor_client_coreHttpResponse *response __attribute__((swift_name("response")));
@property (readonly) int32_t status_ __attribute__((swift_name("status_")));
@property (readonly) BOOL success __attribute__((swift_name("success")));
- (instancetype)initWithResponse:(TSAPIKtor_client_coreHttpResponse *)response provider:(id<TSAPIBodyProvider>)provider __attribute__((swift_name("init(response:provider:)"))) __attribute__((objc_designated_initializer));

/**
 * @note This method converts instances of CancellationException to errors.
 * Other uncaught Kotlin exceptions are fatal.
*/
- (void)bodyWithCompletionHandler:(void (^)(T _Nullable, NSError * _Nullable))completionHandler __attribute__((swift_name("body(completionHandler:)")));

/**
 * @note This method converts instances of CancellationException to errors.
 * Other uncaught Kotlin exceptions are fatal.
*/
- (void)typedBodyType:(TSAPIKtor_utilsTypeInfo *)type completionHandler:(void (^)(id _Nullable, NSError * _Nullable))completionHandler __attribute__((swift_name("typedBody(type:completionHandler:)")));
@end

...

What we noticed is that when coroutinesInterop.set(true) the TSAPIHttpResponse entry is completely missing from .apinotes

It's included with coroutinesInterop.set(false):

- Name: "TSAPIHttpResponse"
  SwiftName: "HttpResponse"
  Properties:
  - Name: "headers"
    PropertyKind: "Instance"
    SwiftName: "headers"
    Type: "NSDictionary<NSString *, NSArray<NSString *> *> * _Nonnull"
  - Name: "provider"
    PropertyKind: "Instance"
    SwiftName: "provider"
    Type: "id<TSAPIBodyProvider> _Nonnull"
  - Name: "response"
    PropertyKind: "Instance"
    SwiftName: "response"
    Type: "TSAPIKtor_client_coreHttpResponse * _Nonnull"
  - Name: "status_"
    PropertyKind: "Instance"
    SwiftName: "status"
    Type: "int32_t"
  - Name: "success"
    PropertyKind: "Instance"
    SwiftName: "success"
    Type: "BOOL"
  Methods:
  - Selector: "initWithResponse:provider:"
    MethodKind: "Instance"
    SwiftName: "init(response:provider:)"
    Parameters:
    - Position: 0
      Type: "TSAPIKtor_client_coreHttpResponse * _Nonnull"
    - Position: 1
      Type: "id<TSAPIBodyProvider> _Nonnull"
  - Selector: "bodyWithCompletionHandler:"
    MethodKind: "Instance"
    SwiftName: "body(completionHandler:)"
    ResultType: "void"
    Parameters:
    - Position: 0
      Type: "void (^ _Nonnull)(T _Nullable, NSError * _Nullable)"
  - Selector: "isEqual:"
    MethodKind: "Instance"
    SwiftName: "isEqual(_:)"
    ResultType: "BOOL"
    Parameters:
    - Position: 0
      Type: "id _Nullable"
  - Selector: "hash"
    MethodKind: "Instance"
    SwiftName: "hash()"
    ResultType: "NSUInteger"
  - Selector: "description"
    MethodKind: "Instance"
    SwiftName: "description()"
    ResultType: "NSString * _Nonnull"
  - Selector: "typedBodyType:completionHandler:"
    MethodKind: "Instance"
    SwiftName: "typedBody(type:completionHandler:)"
    ResultType: "void"
    Parameters:
    - Position: 0
      Type: "TSAPIKtor_utilsTypeInfo * _Nonnull"
    - Position: 1
      Type: "void (^ _Nonnull)(id _Nullable, NSError * _Nullable)"
  - Selector: "mapBlock:"
    MethodKind: "Instance"
    SwiftName: "map(block:)"
    ResultType: "TSAPIHttpResponse<id> * _Nonnull"
    Parameters:
    - Position: 0
      Type: "id _Nonnull (^ _Nonnull)(id _Nonnull)"

What versions of SKIE, Kotlin, and Gradle do you use?

Am I missing something & can I provide you anything else to investigate this? :)

Update: Noticed that this works:

skie(httpResponse).body()

Is there a way to get it working without this workaround?

TadeasKriz commented 8 months ago

https://skie.touchlab.co/features/suspend#generic-classes - We tried and couldn't find a way around this since we need to do extension X, but if X is generic Objective-C class, we can't extend it (we can but can't use the generic type). If you have specific types, you can do:

extension HttpResponse where T == String {
  func body() async throws -> String {
    return try await skie(self).body()
  }
}