Native: add KotlinThrowable.asError() to the generated Obj-C framework
This method can be useful when overriding a throwing Kotlin method in Swift or Obj-C. In this case, a user can call asError to wrap Kotlin exception to (NS)Error and then throw it to Kotlin, which will unwrap it back. ^KT-45127 Fixed
This commit is contained in:
committed by
Space
parent
c988ecf59b
commit
d531df1643
@@ -961,6 +961,10 @@ public abstract class KotlinBuiltIns {
|
||||
return isConstructedFromGivenClass(type, FqNames.throwable);
|
||||
}
|
||||
|
||||
public static boolean isThrowable(@NotNull ClassDescriptor descriptor) {
|
||||
return classFqNameEquals(descriptor, FqNames.throwable.toUnsafe());
|
||||
}
|
||||
|
||||
public static boolean isKClass(@NotNull ClassDescriptor descriptor) {
|
||||
return classFqNameEquals(descriptor, FqNames.kClass);
|
||||
}
|
||||
|
||||
+1
@@ -497,6 +497,7 @@ internal class Llvm(val context: Context, val llvmModule: LLVMModuleRef) {
|
||||
val Kotlin_ObjCExport_GetAssociatedObject by lazyRtFunction
|
||||
val Kotlin_ObjCExport_AbstractMethodCalled by lazyRtFunction
|
||||
val Kotlin_ObjCExport_RethrowExceptionAsNSError by lazyRtFunction
|
||||
val Kotlin_ObjCExport_WrapExceptionToNSError by lazyRtFunction
|
||||
val Kotlin_ObjCExport_RethrowNSErrorAsException by lazyRtFunction
|
||||
val Kotlin_ObjCExport_AllocInstanceWithAssociatedObject by lazyRtFunction
|
||||
val Kotlin_ObjCExport_createContinuationArgument by lazyRtFunction
|
||||
|
||||
+19
@@ -1291,6 +1291,9 @@ private fun ObjCExportCodeGenerator.createTypeAdapter(
|
||||
createObjectInstanceAdapter(irClass, it.selector)
|
||||
}
|
||||
}
|
||||
ObjCKotlinThrowableAsErrorMethod -> {
|
||||
adapters += createThrowableAsErrorAdapter()
|
||||
}
|
||||
is ObjCMethodForKotlinMethod -> {} // Handled below.
|
||||
}.let {} // Force exhaustive.
|
||||
}
|
||||
@@ -1576,6 +1579,22 @@ private fun ObjCExportCodeGenerator.createEnumValuesAdapter(
|
||||
return objCToKotlinMethodAdapter(selector, methodBridge, imp)
|
||||
}
|
||||
|
||||
private fun ObjCExportCodeGenerator.createThrowableAsErrorAdapter(): ObjCExportCodeGenerator.ObjCToKotlinMethodAdapter {
|
||||
val methodBridge = MethodBridge(
|
||||
returnBridge = MethodBridge.ReturnValue.Mapped(ReferenceBridge),
|
||||
receiver = MethodBridgeReceiver.Instance,
|
||||
valueParameters = emptyList()
|
||||
)
|
||||
|
||||
val imp = generateObjCImpBy(methodBridge) {
|
||||
val exception = objCReferenceToKotlin(param(0), Lifetime.ARGUMENT)
|
||||
ret(callFromBridge(context.llvm.Kotlin_ObjCExport_WrapExceptionToNSError, listOf(exception)))
|
||||
}
|
||||
|
||||
val selector = ObjCExportNamer.kotlinThrowableAsErrorMethodName
|
||||
return objCToKotlinMethodAdapter(selector, methodBridge, imp)
|
||||
}
|
||||
|
||||
private fun objCFunctionType(context: Context, methodBridge: MethodBridge): LLVMTypeRef {
|
||||
val paramTypes = methodBridge.paramBridges.map { it.objCType }
|
||||
|
||||
|
||||
+7
@@ -9,6 +9,7 @@ import org.jetbrains.kotlin.backend.konan.descriptors.contributedMethods
|
||||
import org.jetbrains.kotlin.backend.konan.descriptors.enumEntries
|
||||
import org.jetbrains.kotlin.backend.konan.descriptors.isArray
|
||||
import org.jetbrains.kotlin.backend.konan.descriptors.isInterface
|
||||
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
|
||||
import org.jetbrains.kotlin.descriptors.*
|
||||
import org.jetbrains.kotlin.ir.symbols.*
|
||||
import org.jetbrains.kotlin.ir.util.SymbolTable
|
||||
@@ -88,6 +89,10 @@ internal fun ObjCExportedInterface.createCodeSpec(symbolTable: SymbolTable): Obj
|
||||
}
|
||||
}
|
||||
|
||||
if (KotlinBuiltIns.isThrowable(descriptor)) {
|
||||
methods += ObjCKotlinThrowableAsErrorMethod
|
||||
}
|
||||
|
||||
val categoryMethods = categoryMembers[descriptor].orEmpty().toObjCMethods()
|
||||
|
||||
val superClassNotAny = descriptor.getSuperClassNotAny()
|
||||
@@ -149,6 +154,8 @@ internal class ObjCClassMethodForKotlinEnumValues(
|
||||
|
||||
internal class ObjCGetterForObjectInstance(val selector: String) : ObjCMethodSpec()
|
||||
|
||||
internal object ObjCKotlinThrowableAsErrorMethod : ObjCMethodSpec()
|
||||
|
||||
internal sealed class ObjCTypeSpec(val binaryName: String)
|
||||
|
||||
internal sealed class ObjCTypeForKotlinType(
|
||||
|
||||
+16
@@ -386,6 +386,10 @@ internal class ObjCExportTranslatorImpl(
|
||||
}
|
||||
|
||||
translateClassMembers(descriptor, genericExportScope)
|
||||
|
||||
if (KotlinBuiltIns.isThrowable(descriptor)) {
|
||||
add { buildThrowableAsErrorMethod() }
|
||||
}
|
||||
}
|
||||
|
||||
val attributes = if (descriptor.isFinalOrEnum) listOf(OBJC_SUBCLASSING_RESTRICTED) else emptyList()
|
||||
@@ -406,6 +410,18 @@ internal class ObjCExportTranslatorImpl(
|
||||
)
|
||||
}
|
||||
|
||||
private fun buildThrowableAsErrorMethod(): ObjCMethod {
|
||||
val asError = ObjCExportNamer.kotlinThrowableAsErrorMethodName
|
||||
return ObjCMethod(
|
||||
descriptor = null,
|
||||
isInstanceMethod = true,
|
||||
returnType = ObjCClassType("NSError"),
|
||||
selectors = listOf(asError),
|
||||
parameters = emptyList(),
|
||||
attributes = listOf(swiftNameAttribute("$asError()"))
|
||||
)
|
||||
}
|
||||
|
||||
private fun mapTypeConstructorParameters(descriptor: ClassDescriptor): List<ObjCGenericTypeParameterDeclaration> {
|
||||
if (objcGenerics) {
|
||||
return descriptor.typeConstructor.parameters.map {
|
||||
|
||||
+4
@@ -66,6 +66,10 @@ interface ObjCExportNamer {
|
||||
val mutableSetName: ClassOrProtocolName
|
||||
val mutableMapName: ClassOrProtocolName
|
||||
val kotlinNumberName: ClassOrProtocolName
|
||||
|
||||
companion object {
|
||||
internal val kotlinThrowableAsErrorMethodName: String = "asError"
|
||||
}
|
||||
}
|
||||
|
||||
fun createNamer(moduleDescriptor: ModuleDescriptor,
|
||||
|
||||
@@ -805,6 +805,44 @@ __attribute__((swift_name("OverrideMethodsOfAnyKt")))
|
||||
+ (BOOL)testObj:(id)obj other:(id)other swift:(BOOL)swift error:(NSError * _Nullable * _Nullable)error __attribute__((swift_name("test(obj:other:swift:)")));
|
||||
@end;
|
||||
|
||||
__attribute__((objc_subclassing_restricted))
|
||||
__attribute__((swift_name("ThrowableAsError")))
|
||||
@interface KtThrowableAsError : KtKotlinThrowable
|
||||
- (instancetype)init __attribute__((swift_name("init()"))) __attribute__((objc_designated_initializer));
|
||||
+ (instancetype)new __attribute__((availability(swift, unavailable, message="use object initializers instead")));
|
||||
- (instancetype)initWithMessage:(NSString * _Nullable)message __attribute__((swift_name("init(message:)"))) __attribute__((objc_designated_initializer)) __attribute__((unavailable));
|
||||
- (instancetype)initWithCause:(KtKotlinThrowable * _Nullable)cause __attribute__((swift_name("init(cause:)"))) __attribute__((objc_designated_initializer)) __attribute__((unavailable));
|
||||
- (instancetype)initWithMessage:(NSString * _Nullable)message cause:(KtKotlinThrowable * _Nullable)cause __attribute__((swift_name("init(message:cause:)"))) __attribute__((objc_designated_initializer)) __attribute__((unavailable));
|
||||
@end;
|
||||
|
||||
__attribute__((swift_name("ThrowsThrowableAsError")))
|
||||
@protocol KtThrowsThrowableAsError
|
||||
@required
|
||||
|
||||
/**
|
||||
@note This method converts all Kotlin exceptions to errors.
|
||||
*/
|
||||
- (BOOL)throwErrorAndReturnError:(NSError * _Nullable * _Nullable)error __attribute__((swift_name("throwError()")));
|
||||
@end;
|
||||
|
||||
__attribute__((swift_name("ThrowsThrowableAsErrorSuspend")))
|
||||
@protocol KtThrowsThrowableAsErrorSuspend
|
||||
@required
|
||||
|
||||
/**
|
||||
@note This method converts instances of CancellationException to errors.
|
||||
Other uncaught Kotlin exceptions are fatal.
|
||||
*/
|
||||
- (void)throwErrorWithCompletionHandler:(void (^)(KtKotlinUnit * _Nullable, NSError * _Nullable))completionHandler __attribute__((swift_name("throwError(completionHandler:)")));
|
||||
@end;
|
||||
|
||||
__attribute__((objc_subclassing_restricted))
|
||||
__attribute__((swift_name("ThrowableAsErrorKt")))
|
||||
@interface KtThrowableAsErrorKt : KtBase
|
||||
+ (KtThrowableAsError * _Nullable)callAndCatchThrowableAsErrorThrowsThrowableAsError:(id<KtThrowsThrowableAsError>)throwsThrowableAsError __attribute__((swift_name("callAndCatchThrowableAsError(throwsThrowableAsError:)")));
|
||||
+ (KtThrowableAsError * _Nullable)callAndCatchThrowableAsErrorSuspendThrowsThrowableAsErrorSuspend:(id<KtThrowsThrowableAsErrorSuspend>)throwsThrowableAsErrorSuspend __attribute__((swift_name("callAndCatchThrowableAsErrorSuspend(throwsThrowableAsErrorSuspend:)")));
|
||||
@end;
|
||||
|
||||
__attribute__((objc_subclassing_restricted))
|
||||
__attribute__((swift_name("ThrowsEmptyKt")))
|
||||
@interface KtThrowsEmptyKt : KtBase
|
||||
|
||||
@@ -778,6 +778,44 @@ __attribute__((swift_name("OverrideMethodsOfAnyKt")))
|
||||
+ (BOOL)testObj:(id)obj other:(id)other swift:(BOOL)swift error:(NSError * _Nullable * _Nullable)error __attribute__((swift_name("test(obj:other:swift:)")));
|
||||
@end;
|
||||
|
||||
__attribute__((objc_subclassing_restricted))
|
||||
__attribute__((swift_name("ThrowableAsError")))
|
||||
@interface KtThrowableAsError : KtKotlinThrowable
|
||||
- (instancetype)init __attribute__((swift_name("init()"))) __attribute__((objc_designated_initializer));
|
||||
+ (instancetype)new __attribute__((availability(swift, unavailable, message="use object initializers instead")));
|
||||
- (instancetype)initWithMessage:(NSString * _Nullable)message __attribute__((swift_name("init(message:)"))) __attribute__((objc_designated_initializer)) __attribute__((unavailable));
|
||||
- (instancetype)initWithCause:(KtKotlinThrowable * _Nullable)cause __attribute__((swift_name("init(cause:)"))) __attribute__((objc_designated_initializer)) __attribute__((unavailable));
|
||||
- (instancetype)initWithMessage:(NSString * _Nullable)message cause:(KtKotlinThrowable * _Nullable)cause __attribute__((swift_name("init(message:cause:)"))) __attribute__((objc_designated_initializer)) __attribute__((unavailable));
|
||||
@end;
|
||||
|
||||
__attribute__((swift_name("ThrowsThrowableAsError")))
|
||||
@protocol KtThrowsThrowableAsError
|
||||
@required
|
||||
|
||||
/**
|
||||
@note This method converts all Kotlin exceptions to errors.
|
||||
*/
|
||||
- (BOOL)throwErrorAndReturnError:(NSError * _Nullable * _Nullable)error __attribute__((swift_name("throwError()")));
|
||||
@end;
|
||||
|
||||
__attribute__((swift_name("ThrowsThrowableAsErrorSuspend")))
|
||||
@protocol KtThrowsThrowableAsErrorSuspend
|
||||
@required
|
||||
|
||||
/**
|
||||
@note This method converts instances of CancellationException to errors.
|
||||
Other uncaught Kotlin exceptions are fatal.
|
||||
*/
|
||||
- (void)throwErrorWithCompletionHandler:(void (^)(KtKotlinUnit * _Nullable, NSError * _Nullable))completionHandler __attribute__((swift_name("throwError(completionHandler:)")));
|
||||
@end;
|
||||
|
||||
__attribute__((objc_subclassing_restricted))
|
||||
__attribute__((swift_name("ThrowableAsErrorKt")))
|
||||
@interface KtThrowableAsErrorKt : KtBase
|
||||
+ (KtThrowableAsError * _Nullable)callAndCatchThrowableAsErrorThrowsThrowableAsError:(id<KtThrowsThrowableAsError>)throwsThrowableAsError __attribute__((swift_name("callAndCatchThrowableAsError(throwsThrowableAsError:)")));
|
||||
+ (KtThrowableAsError * _Nullable)callAndCatchThrowableAsErrorSuspendThrowsThrowableAsErrorSuspend:(id<KtThrowsThrowableAsErrorSuspend>)throwsThrowableAsErrorSuspend __attribute__((swift_name("callAndCatchThrowableAsErrorSuspend(throwsThrowableAsErrorSuspend:)")));
|
||||
@end;
|
||||
|
||||
__attribute__((objc_subclassing_restricted))
|
||||
__attribute__((swift_name("ThrowsEmptyKt")))
|
||||
@interface KtThrowsEmptyKt : KtBase
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
package throwableAsError
|
||||
|
||||
import kotlin.coroutines.*
|
||||
|
||||
class ThrowableAsError : Throwable()
|
||||
|
||||
interface ThrowsThrowableAsError {
|
||||
@Throws(Throwable::class)
|
||||
fun throwError()
|
||||
}
|
||||
|
||||
fun callAndCatchThrowableAsError(throwsThrowableAsError: ThrowsThrowableAsError): ThrowableAsError? {
|
||||
try {
|
||||
throwsThrowableAsError.throwError()
|
||||
} catch (e: ThrowableAsError) {
|
||||
return e
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
interface ThrowsThrowableAsErrorSuspend {
|
||||
suspend fun throwError()
|
||||
}
|
||||
|
||||
fun callAndCatchThrowableAsErrorSuspend(throwsThrowableAsErrorSuspend: ThrowsThrowableAsErrorSuspend): ThrowableAsError? {
|
||||
var throwable: ThrowableAsError? = null
|
||||
suspend {
|
||||
throwsThrowableAsErrorSuspend.throwError()
|
||||
}.startCoroutine(object : Continuation<Unit> {
|
||||
override val context = EmptyCoroutineContext
|
||||
override fun resumeWith(result: Result<Unit>) {
|
||||
throwable = result.exceptionOrNull() as? ThrowableAsError
|
||||
}
|
||||
})
|
||||
|
||||
return throwable
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
import Kt
|
||||
|
||||
private func testUnwrapsToSame() throws {
|
||||
let throwable = ThrowableAsError()
|
||||
try assertSame(actual: throwable.asError().kotlinException as AnyObject, expected: throwable)
|
||||
}
|
||||
|
||||
private func testCanThrowAndCatch() throws {
|
||||
let throwable = ThrowableAsError()
|
||||
let impl = ThrowsThrowableAsErrorImpl(throwable: throwable)
|
||||
let caught = ThrowableAsErrorKt.callAndCatchThrowableAsError(throwsThrowableAsError: impl)
|
||||
try assertSame(actual: caught, expected: throwable)
|
||||
}
|
||||
|
||||
private class ThrowsThrowableAsErrorImpl : ThrowsThrowableAsError {
|
||||
var throwable: KotlinThrowable
|
||||
|
||||
init(throwable: KotlinThrowable) {
|
||||
self.throwable = throwable
|
||||
}
|
||||
|
||||
func throwError() throws {
|
||||
throw throwable.asError()
|
||||
}
|
||||
}
|
||||
|
||||
private func testCanThrowAndCatchSuspend() throws {
|
||||
let throwable = ThrowableAsError()
|
||||
let impl = ThrowsThrowableAsErrorSuspendImpl(throwable: throwable)
|
||||
let caught = ThrowableAsErrorKt.callAndCatchThrowableAsErrorSuspend(throwsThrowableAsErrorSuspend: impl)
|
||||
try assertSame(actual: caught, expected: throwable)
|
||||
}
|
||||
|
||||
private class ThrowsThrowableAsErrorSuspendImpl : ThrowsThrowableAsErrorSuspend {
|
||||
var throwable: KotlinThrowable
|
||||
|
||||
init(throwable: KotlinThrowable) {
|
||||
self.throwable = throwable
|
||||
}
|
||||
|
||||
func throwError(completionHandler: @escaping (KotlinUnit?, Error?) -> Void) {
|
||||
completionHandler(nil, throwable.asError())
|
||||
}
|
||||
}
|
||||
|
||||
class ThrowableAsErrorTests : SimpleTestProvider {
|
||||
override init() {
|
||||
super.init()
|
||||
|
||||
test("TestUnwrapsToSame", testUnwrapsToSame)
|
||||
test("TestCanThrowAndCatch", testCanThrowAndCatch)
|
||||
test("TestCanThrowAndCatchSuspend", testCanThrowAndCatchSuspend)
|
||||
}
|
||||
}
|
||||
@@ -6,4 +6,5 @@
|
||||
#import "Types.h"
|
||||
|
||||
extern "C" id Kotlin_ObjCExport_ExceptionAsNSError(KRef exception, const TypeInfo** types);
|
||||
extern "C" id Kotlin_ObjCExport_WrapExceptionToNSError(KRef exception);
|
||||
extern "C" OBJ_GETTER(Kotlin_ObjCExport_NSErrorAsException, id error);
|
||||
|
||||
@@ -70,6 +70,10 @@ extern "C" id Kotlin_ObjCExport_ExceptionAsNSError(KRef exception, const TypeInf
|
||||
TerminateWithUnhandledException(exception);
|
||||
}
|
||||
|
||||
return Kotlin_ObjCExport_WrapExceptionToNSError(exception);
|
||||
}
|
||||
|
||||
extern "C" id Kotlin_ObjCExport_WrapExceptionToNSError(KRef exception) {
|
||||
NSMutableDictionary<NSErrorUserInfoKey, id>* userInfo = [[NSMutableDictionary new] autorelease];
|
||||
userInfo[@"KotlinException"] = Kotlin_ObjCExport_refToObjC(exception);
|
||||
userInfo[@"KotlinExceptionOrigin"] = @(&kotlinExceptionOriginChar); // Support for different Kotlin runtimes loaded.
|
||||
|
||||
Reference in New Issue
Block a user