Support implicit @Throws(CancellationException::class) in ObjCExport
This commit is contained in:
committed by
SvyatoslavScherbina
parent
7978f0ac8d
commit
d5cbfd5420
+1
@@ -22,6 +22,7 @@ object KonanFqNames {
|
||||
val nonNullNativePtr = internalPackageName.child(Name.identifier(NON_NULL_NATIVE_PTR_NAME)).toUnsafe()
|
||||
val Vector128 = packageName.child(Name.identifier(VECTOR128))
|
||||
val throws = FqName("kotlin.Throws")
|
||||
val cancellationException = FqName("kotlin.coroutines.cancellation.CancellationException")
|
||||
val threadLocal = FqName("kotlin.native.concurrent.ThreadLocal")
|
||||
val sharedImmutable = FqName("kotlin.native.concurrent.SharedImmutable")
|
||||
val frozen = FqName("kotlin.native.internal.Frozen")
|
||||
|
||||
+2
@@ -409,6 +409,8 @@ internal class KonanSymbols(
|
||||
.filterNot { it.isExpect }.single().getter!!
|
||||
)
|
||||
|
||||
val cancellationException = topLevelClass(KonanFqNames.cancellationException)
|
||||
|
||||
val kotlinResult = topLevelClass("kotlin.Result")
|
||||
|
||||
val kotlinResultGetOrThrow = symbolTable.referenceSimpleFunction(
|
||||
|
||||
+15
-12
@@ -9,10 +9,7 @@ import llvm.*
|
||||
import org.jetbrains.kotlin.backend.common.ir.simpleFunctions
|
||||
import org.jetbrains.kotlin.backend.konan.*
|
||||
import org.jetbrains.kotlin.backend.konan.descriptors.*
|
||||
import org.jetbrains.kotlin.backend.konan.ir.allParameters
|
||||
import org.jetbrains.kotlin.backend.konan.ir.isOverridable
|
||||
import org.jetbrains.kotlin.backend.konan.ir.isUnit
|
||||
import org.jetbrains.kotlin.backend.konan.ir.llvmSymbolOrigin
|
||||
import org.jetbrains.kotlin.backend.konan.ir.*
|
||||
import org.jetbrains.kotlin.backend.konan.llvm.*
|
||||
import org.jetbrains.kotlin.backend.konan.llvm.objc.ObjCCodeGenerator
|
||||
import org.jetbrains.kotlin.backend.konan.llvm.objc.ObjCDataGenerator
|
||||
@@ -890,7 +887,7 @@ private fun ObjCExportCodeGenerator.generateObjCImp(
|
||||
|
||||
private fun ObjCExportCodeGenerator.generateExceptionTypeInfoArray(baseMethod: IrFunction): LLVMValueRef =
|
||||
exceptionTypeInfoArrays.getOrPut(baseMethod) {
|
||||
val types = computesThrowsClasses(baseMethod)
|
||||
val types = effectiveThrowsClasses(baseMethod, symbols)
|
||||
generateTypeInfoArray(types.toSet())
|
||||
}.llvm
|
||||
|
||||
@@ -900,16 +897,22 @@ private fun ObjCExportCodeGenerator.generateTypeInfoArray(types: Set<IrClass>):
|
||||
codegen.staticData.placeGlobalConstArray("", codegen.kTypeInfoPtr, typeInfos)
|
||||
}
|
||||
|
||||
private fun computesThrowsClasses(method: IrFunction): List<IrClass> {
|
||||
// Note: frontend ensures that all topmost overridden methods have equal @Throws annotations.
|
||||
// However due to linking different versions of libraries IR could end up not meeting this condition.
|
||||
// Handling this gracefully below.
|
||||
|
||||
private fun effectiveThrowsClasses(method: IrFunction, symbols: KonanSymbols): List<IrClass> {
|
||||
if (method is IrSimpleFunction && method.overriddenSymbols.isNotEmpty()) {
|
||||
return computesThrowsClasses(method.overriddenSymbols.first().owner)
|
||||
return effectiveThrowsClasses(method.overriddenSymbols.first().owner, symbols)
|
||||
}
|
||||
|
||||
val throwsVararg = method.annotations.findAnnotation(KonanFqNames.throws)?.getValueArgument(0)
|
||||
val throwsAnnotation = method.annotations.findAnnotation(KonanFqNames.throws)
|
||||
?: return if (method.isSuspend) {
|
||||
listOf(symbols.cancellationException.owner)
|
||||
} else {
|
||||
// Note: frontend ensures that all topmost overridden methods have (equal) @Throws annotations.
|
||||
// However due to linking different versions of libraries IR could end up not meeting this condition.
|
||||
// Handling missing annotation gracefully:
|
||||
emptyList()
|
||||
}
|
||||
|
||||
val throwsVararg = throwsAnnotation.getValueArgument(0)
|
||||
?: return emptyList()
|
||||
|
||||
if (throwsVararg !is IrVararg) error(method.getContainingFile(), throwsVararg, "unexpected vararg")
|
||||
|
||||
+29
-8
@@ -652,16 +652,37 @@ internal class ObjCExportTranslatorImpl(
|
||||
}
|
||||
|
||||
private fun exportThrown(method: FunctionDescriptor) {
|
||||
if (!method.kind.isReal) return
|
||||
val throwsAnnotation = method.annotations.findAnnotation(KonanFqNames.throws) ?: return
|
||||
|
||||
val arguments = (throwsAnnotation.allValueArguments.values.single() as ArrayValue).value
|
||||
for (argument in arguments) {
|
||||
val classDescriptor = TypeUtils.getClassDescriptor((argument as KClassValue).getArgumentType(method.module)) ?: continue
|
||||
generator?.requireClassOrInterface(classDescriptor)
|
||||
}
|
||||
getDefinedThrows(method)
|
||||
?.mapNotNull { method.module.findClassAcrossModuleDependencies(it) }
|
||||
?.forEach { generator?.requireClassOrInterface(it) }
|
||||
}
|
||||
|
||||
private fun getDefinedThrows(method: FunctionDescriptor): Sequence<ClassId>? {
|
||||
if (!method.kind.isReal) return null
|
||||
|
||||
val throwsAnnotation = method.annotations.findAnnotation(KonanFqNames.throws)
|
||||
|
||||
if (throwsAnnotation != null) {
|
||||
val argumentsArrayValue = throwsAnnotation.firstArgument() as? ArrayValue
|
||||
return argumentsArrayValue?.value?.asSequence().orEmpty()
|
||||
.filterIsInstance<KClassValue>()
|
||||
.mapNotNull {
|
||||
when (val value = it.value) {
|
||||
is KClassValue.Value.NormalClass -> value.classId
|
||||
is KClassValue.Value.LocalClass -> null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (method.isSuspend && method.overriddenDescriptors.isEmpty()) {
|
||||
return implicitSuspendThrows
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
private val implicitSuspendThrows = sequenceOf(ClassId.topLevel(KonanFqNames.cancellationException))
|
||||
|
||||
private fun mapReturnType(returnBridge: MethodBridge.ReturnValue, method: FunctionDescriptor, objCExportScope: ObjCExportScope): ObjCType = when (returnBridge) {
|
||||
MethodBridge.ReturnValue.Suspend,
|
||||
MethodBridge.ReturnValue.Void -> ObjCVoidType
|
||||
|
||||
@@ -139,4 +139,28 @@ fun callSuspendBridge(bridge: AbstractSuspendBridge, resultHolder: ResultHolder<
|
||||
callSuspendBridgeImpl(bridge)
|
||||
callAbstractSuspendBridgeImpl(bridge)
|
||||
}.startCoroutine(ResultHolderCompletion(resultHolder))
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun throwCancellationException(): Unit {
|
||||
val exception = CancellationException("coroutine is cancelled")
|
||||
|
||||
// Note: frontend checker hardcodes fq names of CancellationException super classes (see NativeThrowsChecker).
|
||||
// This is our best effort to keep that list in sync with actual stdlib code:
|
||||
assertTrue(exception is kotlin.Throwable)
|
||||
assertTrue(exception is kotlin.Exception)
|
||||
assertTrue(exception is kotlin.RuntimeException)
|
||||
assertTrue(exception is kotlin.IllegalStateException)
|
||||
assertTrue(exception is kotlin.coroutines.cancellation.CancellationException)
|
||||
|
||||
throw exception
|
||||
}
|
||||
|
||||
abstract class ThrowCancellationException {
|
||||
internal abstract suspend fun throwCancellationException()
|
||||
}
|
||||
|
||||
class ThrowCancellationExceptionImpl : ThrowCancellationException() {
|
||||
public override suspend fun throwCancellationException() {
|
||||
throw CancellationException()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -221,6 +221,38 @@ private func testBridges() throws {
|
||||
try assertSame(actual: resultHolder.result, expected: KotlinUnit())
|
||||
}
|
||||
|
||||
private func testImplicitThrows1() throws {
|
||||
var result: KotlinUnit? = nil
|
||||
var error: Error? = nil
|
||||
var completionCalled = 0
|
||||
|
||||
CoroutinesKt.throwCancellationException { _result, _error in
|
||||
completionCalled += 1
|
||||
result = _result
|
||||
error = _error
|
||||
}
|
||||
|
||||
try assertEquals(actual: completionCalled, expected: 1)
|
||||
try assertNil(result)
|
||||
try assertTrue(error?.kotlinException is KotlinCancellationException)
|
||||
}
|
||||
|
||||
private func testImplicitThrows2() throws {
|
||||
var result: KotlinUnit? = nil
|
||||
var error: Error? = nil
|
||||
var completionCalled = 0
|
||||
|
||||
ThrowCancellationExceptionImpl().throwCancellationException { _result, _error in
|
||||
completionCalled += 1
|
||||
result = _result
|
||||
error = _error
|
||||
}
|
||||
|
||||
try assertEquals(actual: completionCalled, expected: 1)
|
||||
try assertNil(result)
|
||||
try assertTrue(error?.kotlinException is KotlinCancellationException)
|
||||
}
|
||||
|
||||
class CoroutinesTests : SimpleTestProvider {
|
||||
override init() {
|
||||
super.init()
|
||||
@@ -229,5 +261,7 @@ class CoroutinesTests : SimpleTestProvider {
|
||||
test("TestCall", testCall)
|
||||
test("TestOverride", testOverride)
|
||||
test("TestBridges", testBridges)
|
||||
test("TestImplicitThrows1", testImplicitThrows1)
|
||||
test("TestImplicitThrows2", testImplicitThrows2)
|
||||
}
|
||||
}
|
||||
@@ -179,6 +179,20 @@ __attribute__((swift_name("AbstractSuspendBridge")))
|
||||
- (void)nothingAsUnitValue:(KtInt *)value completionHandler:(void (^)(KtKotlinNothing * _Nullable, NSError * _Nullable))completionHandler __attribute__((swift_name("nothingAsUnit(value:completionHandler:)")));
|
||||
@end;
|
||||
|
||||
__attribute__((swift_name("ThrowCancellationException")))
|
||||
@interface KtThrowCancellationException : KtBase
|
||||
- (instancetype)init __attribute__((swift_name("init()"))) __attribute__((objc_designated_initializer));
|
||||
+ (instancetype)new __attribute__((availability(swift, unavailable, message="use object initializers instead")));
|
||||
@end;
|
||||
|
||||
__attribute__((objc_subclassing_restricted))
|
||||
__attribute__((swift_name("ThrowCancellationExceptionImpl")))
|
||||
@interface KtThrowCancellationExceptionImpl : KtThrowCancellationException
|
||||
- (instancetype)init __attribute__((swift_name("init()"))) __attribute__((objc_designated_initializer));
|
||||
+ (instancetype)new __attribute__((availability(swift, unavailable, message="use object initializers instead")));
|
||||
- (void)throwCancellationExceptionWithCompletionHandler:(void (^)(KtKotlinUnit * _Nullable, NSError * _Nullable))completionHandler __attribute__((swift_name("throwCancellationException(completionHandler:)")));
|
||||
@end;
|
||||
|
||||
__attribute__((objc_subclassing_restricted))
|
||||
__attribute__((swift_name("CoroutinesKt")))
|
||||
@interface KtCoroutinesKt : KtBase
|
||||
@@ -189,6 +203,7 @@ __attribute__((swift_name("CoroutinesKt")))
|
||||
+ (void)callSuspendFunSuspendFun:(id<KtSuspendFun>)suspendFun doYield:(BOOL)doYield doThrow:(BOOL)doThrow resultHolder:(KtResultHolder<KtInt *> *)resultHolder __attribute__((swift_name("callSuspendFun(suspendFun:doYield:doThrow:resultHolder:)")));
|
||||
+ (void)callSuspendFun2SuspendFun:(id<KtSuspendFun>)suspendFun doYield:(BOOL)doYield doThrow:(BOOL)doThrow completionHandler:(void (^)(KtInt * _Nullable, NSError * _Nullable))completionHandler __attribute__((swift_name("callSuspendFun2(suspendFun:doYield:doThrow:completionHandler:)")));
|
||||
+ (BOOL)callSuspendBridgeBridge:(KtAbstractSuspendBridge *)bridge resultHolder:(KtResultHolder<KtKotlinUnit *> *)resultHolder error:(NSError * _Nullable * _Nullable)error __attribute__((swift_name("callSuspendBridge(bridge:resultHolder:)")));
|
||||
+ (void)throwCancellationExceptionWithCompletionHandler:(void (^)(KtKotlinUnit * _Nullable, NSError * _Nullable))completionHandler __attribute__((swift_name("throwCancellationException(completionHandler:)")));
|
||||
@end;
|
||||
|
||||
__attribute__((swift_name("GH4002ArgumentBase")))
|
||||
|
||||
Reference in New Issue
Block a user