Skip to content

Commit 44d6166

Browse files
committed
Fix suspend functions in enums.
1 parent 679b091 commit 44d6166

File tree

10 files changed

+94
-20
lines changed

10 files changed

+94
-20
lines changed

SKIE/acceptance-tests

SKIE/compiler/kotlin-plugin/src/kgp_common/kotlin/co/touchlab/skie/phases/features/enums/ExhaustiveEnumsGenerator.kt

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import co.touchlab.skie.configuration.EnumInterop
44
import co.touchlab.skie.configuration.getConfiguration
55
import co.touchlab.skie.phases.SirPhase
66
import co.touchlab.skie.phases.SkiePhase
7+
import co.touchlab.skie.phases.features.suspend.SuspendGenerator
8+
import co.touchlab.skie.phases.features.suspend.addAvailabilityForAsync
79
import co.touchlab.skie.sir.element.SirClass
810
import co.touchlab.skie.sir.element.SirEnumCase
911
import co.touchlab.skie.sir.element.SirExtension
@@ -51,7 +53,7 @@ object ExhaustiveEnumsGenerator : SirPhase {
5153
private val ClassDescriptor.isEnumInteropEnabled: Boolean
5254
get() = this.getConfiguration(EnumInterop.Enabled)
5355

54-
context(SwiftModelScope)
56+
context(SirPhase.Context)
5557
private fun generate(classSwiftModel: MutableKotlinClassSwiftModel) {
5658
val skieClass = classSwiftModel.generateBridge()
5759

@@ -65,7 +67,7 @@ private fun MutableKotlinClassSwiftModel.configureBridging(skieClass: SirClass)
6567
visibility = SwiftModelVisibility.Replaced
6668
}
6769

68-
context(SwiftModelScope)
70+
context(SirPhase.Context)
6971
private fun KotlinClassSwiftModel.generateBridge(): SirClass {
7072
val skieClass = createBridgingEnum()
7173

@@ -74,7 +76,7 @@ private fun KotlinClassSwiftModel.generateBridge(): SirClass {
7476
return skieClass
7577
}
7678

77-
context(SwiftModelScope)
79+
context(SirPhase.Context)
7880
private fun KotlinClassSwiftModel.createBridgingEnum(): SirClass {
7981
val enum = SirClass(
8082
simpleName = kotlinSirClass.simpleName,
@@ -125,7 +127,7 @@ private fun KotlinClassSwiftModel.addEnumCases(skieClass: SirClass) {
125127
}
126128
}
127129

128-
context(KotlinClassSwiftModel)
130+
context(KotlinClassSwiftModel, SirPhase.Context)
129131
private fun TypeSpec.Builder.addPassthroughForMembers() {
130132
allAccessibleDirectlyCallableMembers
131133
.forEach {
@@ -208,29 +210,46 @@ private fun ExtensionSpec.Builder.addToSwiftConversionMethod(skieClass: SirClass
208210
.build(),
209211
)
210212

213+
context(SirPhase.Context)
211214
private class MemberPassthroughGeneratorVisitor(
212215
private val builder: TypeSpec.Builder,
213216
) : KotlinDirectlyCallableMemberSwiftModelVisitor.Unit {
214217

215218
override fun visit(function: KotlinFunctionSwiftModel) {
216219
if (!function.isSupported) return
217220

218-
builder.addFunction(
221+
if (SuspendGenerator.hasSuspendWrapper(function)) {
222+
val asyncFunction = function.asyncSwiftModelOrNull ?: error("Suspend function must have an async swift model: $function")
223+
224+
builder.addFunction(asyncFunction)
225+
}
226+
227+
builder.addFunction(function)
228+
}
229+
230+
private val unsupportedFunctionNames = listOf("compareTo(other:)", "hash()", "description()", "isEqual(_:)")
231+
232+
private val KotlinFunctionSwiftModel.isSupported: Boolean
233+
get() = this.name !in unsupportedFunctionNames
234+
235+
private fun TypeSpec.Builder.addFunction(function: KotlinFunctionSwiftModel) {
236+
addFunction(
219237
FunctionSpec.builder(function.identifier)
220238
.addModifiers(Modifier.PUBLIC)
221239
.addFunctionValueParameters(function)
222240
.throws(function.isThrowing)
241+
.async(function.isSuspend)
242+
.apply {
243+
if (function.isSuspend) {
244+
addAvailabilityForAsync()
245+
}
246+
}
223247
.returns(function.returnType.toSwiftPoetTypeName())
224248
.addFunctionBody(function)
225249
.build(),
226250
)
227251
}
228252

229-
private val unsupportedFunctionNames = listOf("compareTo(other:)", "hash()", "description()", "isEqual(_:)")
230-
231-
private val KotlinFunctionSwiftModel.isSupported: Boolean
232-
get() = !this.isSuspend && this.name !in unsupportedFunctionNames
233-
234253
private fun FunctionSpec.Builder.addFunctionValueParameters(function: KotlinFunctionSwiftModel): FunctionSpec.Builder =
235254
this.apply {
236255
function.valueParameters.forEach {
@@ -250,8 +269,9 @@ private class MemberPassthroughGeneratorVisitor(
250269
private fun FunctionSpec.Builder.addFunctionBody(function: KotlinFunctionSwiftModel): FunctionSpec.Builder =
251270
this.addFunctionBodyWithErrorTypeHandling(function) {
252271
addStatement(
253-
"return %L(self as _ObjectiveCType).%N(%L)",
272+
"return %L%L(self as _ObjectiveCType).%N(%L)",
254273
if (function.isThrowing) "try " else "",
274+
if (function.isSuspend) "await " else "",
255275
function.reference,
256276
function.valueParameters.map { CodeBlock.of("%N", it.parameterName) }.joinToCode(", "),
257277
)

SKIE/compiler/kotlin-plugin/src/kgp_common/kotlin/co/touchlab/skie/phases/features/suspend/SuspendGenerator.kt

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,16 @@ import co.touchlab.skie.phases.SkiePhase
1010
import co.touchlab.skie.phases.util.StatefulSirPhase
1111
import co.touchlab.skie.phases.util.doInPhase
1212
import co.touchlab.skie.swiftmodel.SwiftModelVisibility
13+
import co.touchlab.skie.swiftmodel.callable.function.KotlinFunctionSwiftModel
1314
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
1415
import org.jetbrains.kotlin.descriptors.SimpleFunctionDescriptor
1516

1617
object SuspendGenerator : DescriptorModificationPhase {
1718

1819
context(DescriptorModificationPhase.Context)
19-
override fun isActive(): Boolean = SkieConfigurationFlag.Feature_CoroutinesInterop in skieConfiguration.enabledConfigurationFlags
20+
override fun isActive(): Boolean = isEnabled()
21+
22+
private fun SkiePhase.Context.isEnabled(): Boolean = SkieConfigurationFlag.Feature_CoroutinesInterop in skieConfiguration.enabledConfigurationFlags
2023

2124
context(DescriptorModificationPhase.Context)
2225
override fun execute() {
@@ -39,6 +42,10 @@ object SuspendGenerator : DescriptorModificationPhase {
3942
.filter { it.isSupported }
4043
.filter { it.isInteropEnabled }
4144

45+
context(SkiePhase.Context)
46+
fun hasSuspendWrapper(swiftModel: KotlinFunctionSwiftModel): Boolean =
47+
isEnabled() && swiftModel.descriptor.isSupported && swiftModel.descriptor.isInteropEnabled
48+
4249
private val FunctionDescriptor.isSupported: Boolean
4350
get() = this.isSuspend
4451

SKIE/compiler/kotlin-plugin/src/kgp_common/kotlin/co/touchlab/skie/phases/features/suspend/SwiftSuspendGeneratorDelegate.kt

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -58,19 +58,22 @@ class SwiftSuspendGeneratorDelegate(
5858
return if (this.isFromGenericClass) {
5959
skieClassSuspendGenerator.getOrCreateSkieClass(owner)
6060
} else {
61-
owner.primarySirClass
61+
owner.kotlinSirClass
6262
}
6363
}
6464

6565
private val BridgeModel.isFromGenericClass: Boolean
66-
get() = this.originalFunction.owner?.primarySirClass?.typeParameters?.isEmpty()?.not() ?: false
66+
get() = this.originalFunction.owner?.kotlinSirClass?.typeParameters?.isEmpty()?.not() ?: false
67+
68+
private val BridgeModel.isFromBridgedClass: Boolean
69+
get() = this.originalFunction.owner?.bridgedSirClass != null
6770

6871
private fun SirExtension.addSwiftBridgingFunction(bridgeModel: BridgeModel) {
6972
this.swiftPoetBuilderModifications.add {
7073
addFunction(
7174
FunctionSpec.builder(bridgeModel.originalFunction.identifier)
7275
.setScope(bridgeModel)
73-
.addAttribute(AttributeSpec.available("iOS" to "13", "macOS" to "10.15", "watchOS" to "6", "tvOS" to "13", "*" to ""))
76+
.addAvailabilityForAsync()
7477
.async(true)
7578
.throws(true)
7679
.addValueParameters(bridgeModel)
@@ -162,10 +165,14 @@ class SwiftSuspendGeneratorDelegate(
162165
return
163166
}
164167

165-
if (bridgeModel.isFromGenericClass) {
166-
val dispatchReceiverErasedType = bridgeModel.kotlinBridgingFunction.valueParameters.first().type.toSwiftPoetTypeName()
168+
val dispatchReceiverErasedType by lazy {
169+
bridgeModel.kotlinBridgingFunction.valueParameters.first().type.toSwiftPoetTypeName()
170+
}
167171

172+
if (bridgeModel.isFromGenericClass) {
168173
add(CodeBlock.of("%N as! %T", SkieClassSuspendGenerator.kotlinObjectVariableName, dispatchReceiverErasedType))
174+
} else if (bridgeModel.isFromBridgedClass) {
175+
add(CodeBlock.of("self as %T", dispatchReceiverErasedType))
169176
} else {
170177
add(CodeBlock.of("self"))
171178
}
@@ -186,3 +193,6 @@ class SwiftSuspendGeneratorDelegate(
186193
}
187194
}
188195
}
196+
197+
fun FunctionSpec.Builder.addAvailabilityForAsync(): FunctionSpec.Builder =
198+
addAttribute(AttributeSpec.available("iOS" to "13", "macOS" to "10.15", "watchOS" to "6", "tvOS" to "13", "*" to ""))

SKIE/compiler/kotlin-plugin/src/kgp_common/kotlin/co/touchlab/skie/swiftmodel/SwiftModelProvider.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,10 @@ class SwiftModelProvider(
9797
get() = functionSwiftModels[this.original] ?: throwUnknownDescriptor()
9898

9999
override val FunctionDescriptor.asyncSwiftModel: KotlinFunctionSwiftModel
100-
get() = asyncFunctionSwiftModels[this.original] ?: throwUnknownDescriptor()
100+
get() = this.asyncSwiftModelOrNull ?: throwUnknownDescriptor()
101+
102+
override val FunctionDescriptor.asyncSwiftModelOrNull: KotlinFunctionSwiftModel?
103+
get() = asyncFunctionSwiftModels[this.original]
101104

102105
override val ParameterDescriptor.swiftModel: MutableKotlinValueParameterSwiftModel
103106
get() = parameterSwiftModels[this] ?: throwUnknownDescriptor()

SKIE/compiler/kotlin-plugin/src/kgp_common/kotlin/co/touchlab/skie/swiftmodel/SwiftModelScope.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ interface SwiftModelScope {
4747

4848
val FunctionDescriptor.asyncSwiftModel: KotlinFunctionSwiftModel
4949

50+
val FunctionDescriptor.asyncSwiftModelOrNull: KotlinFunctionSwiftModel?
51+
5052
val ParameterDescriptor.swiftModel: KotlinValueParameterSwiftModel
5153

5254
val PropertyDescriptor.swiftModel: KotlinPropertySwiftModel

SKIE/compiler/kotlin-plugin/src/kgp_common/kotlin/co/touchlab/skie/swiftmodel/callable/function/ActualKotlinFunctionSwiftModel.kt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,11 @@ class ActualKotlinFunctionSwiftModel(
5555

5656
override var visibility: SwiftModelVisibility by core::visibility
5757

58+
override val asyncSwiftModelOrNull: KotlinFunctionSwiftModel?
59+
get() = with(swiftModelScope) {
60+
descriptor.asyncSwiftModelOrNull
61+
}
62+
5863
override val owner: KotlinTypeSwiftModel?
5964
get() = with(swiftModelScope) {
6065
descriptor.owner()
@@ -68,7 +73,7 @@ class ActualKotlinFunctionSwiftModel(
6873

6974
override val objCSelector: String by core::objCSelector
7075

71-
override val isSuspend: Boolean = descriptor.isSuspend
76+
override val isSuspend: Boolean = false
7277

7378
override val isThrowing: Boolean by core::isThrowing
7479

SKIE/compiler/kotlin-plugin/src/kgp_common/kotlin/co/touchlab/skie/swiftmodel/callable/function/AsyncKotlinFunctionSwiftModel.kt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,17 @@ class AsyncKotlinFunctionSwiftModel(
2424
override val valueParameters: List<MutableKotlinValueParameterSwiftModel>
2525
get() = delegate.valueParameters.filter { it.origin != KotlinValueParameterSwiftModel.Origin.SuspendCompletion }
2626

27+
// TODO: This is a hack for calling async function wrappers needed until Sir supports functions.
28+
override val reference: String
29+
get() = delegate.core.replacedReference(this)
30+
31+
override val name: String
32+
get() = delegate.core.name(this)
33+
34+
override val isSuspend: Boolean = true
35+
36+
override val isThrowing: Boolean = true
37+
2738
override val returnType: SirType
2839
get() = with(swiftModelScope) {
2940
descriptor.asyncReturnType(

SKIE/compiler/kotlin-plugin/src/kgp_common/kotlin/co/touchlab/skie/swiftmodel/callable/function/KotlinFunctionSwiftModel.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ interface KotlinFunctionSwiftModel : KotlinDirectlyCallableMemberSwiftModel {
2323

2424
val valueParameters: List<KotlinValueParameterSwiftModel>
2525

26+
val asyncSwiftModelOrNull: KotlinFunctionSwiftModel?
27+
2628
val objCSelector: String
2729

2830
val isSuspend: Boolean

SKIE/compiler/kotlin-plugin/src/kgp_common/kotlin/co/touchlab/skie/swiftmodel/callable/function/KotlinFunctionSwiftModelCore.kt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ package co.touchlab.skie.swiftmodel.callable.function
55
import co.touchlab.skie.compilerinject.reflection.reflectors.mapper
66
import co.touchlab.skie.swiftmodel.DescriptorBridgeProvider
77
import co.touchlab.skie.swiftmodel.SwiftModelVisibility
8+
import co.touchlab.skie.swiftmodel.callable.KotlinDirectlyCallableMemberSwiftModel
89
import co.touchlab.skie.swiftmodel.callable.identifierAfterVisibilityChanges
910
import co.touchlab.skie.swiftmodel.callable.parameter.KotlinParameterSwiftModelCore
1011
import co.touchlab.skie.swiftmodel.factory.ObjCTypeProvider
@@ -55,6 +56,19 @@ class KotlinFunctionSwiftModelCore(
5556
"${swiftModel.identifierAfterVisibilityChanges}(${swiftModel.valueParameters.joinToString("") { "${it.argumentLabel}:" }})"
5657
}
5758

59+
fun replacedReference(swiftModel: KotlinFunctionSwiftModel): String =
60+
if (swiftModel.valueParameters.isEmpty()) {
61+
swiftModel.identifierAfterVisibilityChangesWithoutReplaced
62+
} else {
63+
"${swiftModel.identifierAfterVisibilityChangesWithoutReplaced}(${swiftModel.valueParameters.joinToString("") { "${it.argumentLabel}:" }})"
64+
}
65+
66+
private val KotlinDirectlyCallableMemberSwiftModel.identifierAfterVisibilityChangesWithoutReplaced: String
67+
get() = when (visibility) {
68+
SwiftModelVisibility.Replaced -> identifier
69+
else -> identifierAfterVisibilityChanges
70+
}
71+
5872
fun name(swiftModel: KotlinFunctionSwiftModel): String =
5973
if (swiftModel.valueParameters.isEmpty()) "${swiftModel.identifierAfterVisibilityChanges}()" else reference(swiftModel)
6074

0 commit comments

Comments
 (0)