Skip to content

Commit

Permalink
Fix suspend functions in enums.
Browse files Browse the repository at this point in the history
  • Loading branch information
FilipDolnik committed Sep 26, 2023
1 parent 679b091 commit 44d6166
Show file tree
Hide file tree
Showing 10 changed files with 94 additions and 20 deletions.
2 changes: 1 addition & 1 deletion SKIE/acceptance-tests
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import co.touchlab.skie.configuration.EnumInterop
import co.touchlab.skie.configuration.getConfiguration
import co.touchlab.skie.phases.SirPhase
import co.touchlab.skie.phases.SkiePhase
import co.touchlab.skie.phases.features.suspend.SuspendGenerator
import co.touchlab.skie.phases.features.suspend.addAvailabilityForAsync
import co.touchlab.skie.sir.element.SirClass
import co.touchlab.skie.sir.element.SirEnumCase
import co.touchlab.skie.sir.element.SirExtension
Expand Down Expand Up @@ -51,7 +53,7 @@ object ExhaustiveEnumsGenerator : SirPhase {
private val ClassDescriptor.isEnumInteropEnabled: Boolean
get() = this.getConfiguration(EnumInterop.Enabled)

context(SwiftModelScope)
context(SirPhase.Context)
private fun generate(classSwiftModel: MutableKotlinClassSwiftModel) {
val skieClass = classSwiftModel.generateBridge()

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

context(SwiftModelScope)
context(SirPhase.Context)
private fun KotlinClassSwiftModel.generateBridge(): SirClass {
val skieClass = createBridgingEnum()

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

context(SwiftModelScope)
context(SirPhase.Context)
private fun KotlinClassSwiftModel.createBridgingEnum(): SirClass {
val enum = SirClass(
simpleName = kotlinSirClass.simpleName,
Expand Down Expand Up @@ -125,7 +127,7 @@ private fun KotlinClassSwiftModel.addEnumCases(skieClass: SirClass) {
}
}

context(KotlinClassSwiftModel)
context(KotlinClassSwiftModel, SirPhase.Context)
private fun TypeSpec.Builder.addPassthroughForMembers() {
allAccessibleDirectlyCallableMembers
.forEach {
Expand Down Expand Up @@ -208,29 +210,46 @@ private fun ExtensionSpec.Builder.addToSwiftConversionMethod(skieClass: SirClass
.build(),
)

context(SirPhase.Context)
private class MemberPassthroughGeneratorVisitor(
private val builder: TypeSpec.Builder,
) : KotlinDirectlyCallableMemberSwiftModelVisitor.Unit {

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

builder.addFunction(
if (SuspendGenerator.hasSuspendWrapper(function)) {
val asyncFunction = function.asyncSwiftModelOrNull ?: error("Suspend function must have an async swift model: $function")

builder.addFunction(asyncFunction)
}

builder.addFunction(function)
}

private val unsupportedFunctionNames = listOf("compareTo(other:)", "hash()", "description()", "isEqual(_:)")

private val KotlinFunctionSwiftModel.isSupported: Boolean
get() = this.name !in unsupportedFunctionNames

private fun TypeSpec.Builder.addFunction(function: KotlinFunctionSwiftModel) {
addFunction(
FunctionSpec.builder(function.identifier)
.addModifiers(Modifier.PUBLIC)
.addFunctionValueParameters(function)
.throws(function.isThrowing)
.async(function.isSuspend)
.apply {
if (function.isSuspend) {
addAvailabilityForAsync()
}
}
.returns(function.returnType.toSwiftPoetTypeName())
.addFunctionBody(function)
.build(),
)
}

private val unsupportedFunctionNames = listOf("compareTo(other:)", "hash()", "description()", "isEqual(_:)")

private val KotlinFunctionSwiftModel.isSupported: Boolean
get() = !this.isSuspend && this.name !in unsupportedFunctionNames

private fun FunctionSpec.Builder.addFunctionValueParameters(function: KotlinFunctionSwiftModel): FunctionSpec.Builder =
this.apply {
function.valueParameters.forEach {
Expand All @@ -250,8 +269,9 @@ private class MemberPassthroughGeneratorVisitor(
private fun FunctionSpec.Builder.addFunctionBody(function: KotlinFunctionSwiftModel): FunctionSpec.Builder =
this.addFunctionBodyWithErrorTypeHandling(function) {
addStatement(
"return %L(self as _ObjectiveCType).%N(%L)",
"return %L%L(self as _ObjectiveCType).%N(%L)",
if (function.isThrowing) "try " else "",
if (function.isSuspend) "await " else "",
function.reference,
function.valueParameters.map { CodeBlock.of("%N", it.parameterName) }.joinToCode(", "),
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,16 @@ import co.touchlab.skie.phases.SkiePhase
import co.touchlab.skie.phases.util.StatefulSirPhase
import co.touchlab.skie.phases.util.doInPhase
import co.touchlab.skie.swiftmodel.SwiftModelVisibility
import co.touchlab.skie.swiftmodel.callable.function.KotlinFunctionSwiftModel
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.descriptors.SimpleFunctionDescriptor

object SuspendGenerator : DescriptorModificationPhase {

context(DescriptorModificationPhase.Context)
override fun isActive(): Boolean = SkieConfigurationFlag.Feature_CoroutinesInterop in skieConfiguration.enabledConfigurationFlags
override fun isActive(): Boolean = isEnabled()

private fun SkiePhase.Context.isEnabled(): Boolean = SkieConfigurationFlag.Feature_CoroutinesInterop in skieConfiguration.enabledConfigurationFlags

context(DescriptorModificationPhase.Context)
override fun execute() {
Expand All @@ -39,6 +42,10 @@ object SuspendGenerator : DescriptorModificationPhase {
.filter { it.isSupported }
.filter { it.isInteropEnabled }

context(SkiePhase.Context)
fun hasSuspendWrapper(swiftModel: KotlinFunctionSwiftModel): Boolean =
isEnabled() && swiftModel.descriptor.isSupported && swiftModel.descriptor.isInteropEnabled

private val FunctionDescriptor.isSupported: Boolean
get() = this.isSuspend

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,19 +58,22 @@ class SwiftSuspendGeneratorDelegate(
return if (this.isFromGenericClass) {
skieClassSuspendGenerator.getOrCreateSkieClass(owner)
} else {
owner.primarySirClass
owner.kotlinSirClass
}
}

private val BridgeModel.isFromGenericClass: Boolean
get() = this.originalFunction.owner?.primarySirClass?.typeParameters?.isEmpty()?.not() ?: false
get() = this.originalFunction.owner?.kotlinSirClass?.typeParameters?.isEmpty()?.not() ?: false

private val BridgeModel.isFromBridgedClass: Boolean
get() = this.originalFunction.owner?.bridgedSirClass != null

private fun SirExtension.addSwiftBridgingFunction(bridgeModel: BridgeModel) {
this.swiftPoetBuilderModifications.add {
addFunction(
FunctionSpec.builder(bridgeModel.originalFunction.identifier)
.setScope(bridgeModel)
.addAttribute(AttributeSpec.available("iOS" to "13", "macOS" to "10.15", "watchOS" to "6", "tvOS" to "13", "*" to ""))
.addAvailabilityForAsync()
.async(true)
.throws(true)
.addValueParameters(bridgeModel)
Expand Down Expand Up @@ -162,10 +165,14 @@ class SwiftSuspendGeneratorDelegate(
return
}

if (bridgeModel.isFromGenericClass) {
val dispatchReceiverErasedType = bridgeModel.kotlinBridgingFunction.valueParameters.first().type.toSwiftPoetTypeName()
val dispatchReceiverErasedType by lazy {
bridgeModel.kotlinBridgingFunction.valueParameters.first().type.toSwiftPoetTypeName()
}

if (bridgeModel.isFromGenericClass) {
add(CodeBlock.of("%N as! %T", SkieClassSuspendGenerator.kotlinObjectVariableName, dispatchReceiverErasedType))
} else if (bridgeModel.isFromBridgedClass) {
add(CodeBlock.of("self as %T", dispatchReceiverErasedType))
} else {
add(CodeBlock.of("self"))
}
Expand All @@ -186,3 +193,6 @@ class SwiftSuspendGeneratorDelegate(
}
}
}

fun FunctionSpec.Builder.addAvailabilityForAsync(): FunctionSpec.Builder =
addAttribute(AttributeSpec.available("iOS" to "13", "macOS" to "10.15", "watchOS" to "6", "tvOS" to "13", "*" to ""))
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,10 @@ class SwiftModelProvider(
get() = functionSwiftModels[this.original] ?: throwUnknownDescriptor()

override val FunctionDescriptor.asyncSwiftModel: KotlinFunctionSwiftModel
get() = asyncFunctionSwiftModels[this.original] ?: throwUnknownDescriptor()
get() = this.asyncSwiftModelOrNull ?: throwUnknownDescriptor()

override val FunctionDescriptor.asyncSwiftModelOrNull: KotlinFunctionSwiftModel?
get() = asyncFunctionSwiftModels[this.original]

override val ParameterDescriptor.swiftModel: MutableKotlinValueParameterSwiftModel
get() = parameterSwiftModels[this] ?: throwUnknownDescriptor()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ interface SwiftModelScope {

val FunctionDescriptor.asyncSwiftModel: KotlinFunctionSwiftModel

val FunctionDescriptor.asyncSwiftModelOrNull: KotlinFunctionSwiftModel?

val ParameterDescriptor.swiftModel: KotlinValueParameterSwiftModel

val PropertyDescriptor.swiftModel: KotlinPropertySwiftModel
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,11 @@ class ActualKotlinFunctionSwiftModel(

override var visibility: SwiftModelVisibility by core::visibility

override val asyncSwiftModelOrNull: KotlinFunctionSwiftModel?
get() = with(swiftModelScope) {
descriptor.asyncSwiftModelOrNull
}

override val owner: KotlinTypeSwiftModel?
get() = with(swiftModelScope) {
descriptor.owner()
Expand All @@ -68,7 +73,7 @@ class ActualKotlinFunctionSwiftModel(

override val objCSelector: String by core::objCSelector

override val isSuspend: Boolean = descriptor.isSuspend
override val isSuspend: Boolean = false

override val isThrowing: Boolean by core::isThrowing

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,17 @@ class AsyncKotlinFunctionSwiftModel(
override val valueParameters: List<MutableKotlinValueParameterSwiftModel>
get() = delegate.valueParameters.filter { it.origin != KotlinValueParameterSwiftModel.Origin.SuspendCompletion }

// TODO: This is a hack for calling async function wrappers needed until Sir supports functions.
override val reference: String
get() = delegate.core.replacedReference(this)

override val name: String
get() = delegate.core.name(this)

override val isSuspend: Boolean = true

override val isThrowing: Boolean = true

override val returnType: SirType
get() = with(swiftModelScope) {
descriptor.asyncReturnType(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ interface KotlinFunctionSwiftModel : KotlinDirectlyCallableMemberSwiftModel {

val valueParameters: List<KotlinValueParameterSwiftModel>

val asyncSwiftModelOrNull: KotlinFunctionSwiftModel?

val objCSelector: String

val isSuspend: Boolean
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package co.touchlab.skie.swiftmodel.callable.function
import co.touchlab.skie.compilerinject.reflection.reflectors.mapper
import co.touchlab.skie.swiftmodel.DescriptorBridgeProvider
import co.touchlab.skie.swiftmodel.SwiftModelVisibility
import co.touchlab.skie.swiftmodel.callable.KotlinDirectlyCallableMemberSwiftModel
import co.touchlab.skie.swiftmodel.callable.identifierAfterVisibilityChanges
import co.touchlab.skie.swiftmodel.callable.parameter.KotlinParameterSwiftModelCore
import co.touchlab.skie.swiftmodel.factory.ObjCTypeProvider
Expand Down Expand Up @@ -55,6 +56,19 @@ class KotlinFunctionSwiftModelCore(
"${swiftModel.identifierAfterVisibilityChanges}(${swiftModel.valueParameters.joinToString("") { "${it.argumentLabel}:" }})"
}

fun replacedReference(swiftModel: KotlinFunctionSwiftModel): String =
if (swiftModel.valueParameters.isEmpty()) {
swiftModel.identifierAfterVisibilityChangesWithoutReplaced
} else {
"${swiftModel.identifierAfterVisibilityChangesWithoutReplaced}(${swiftModel.valueParameters.joinToString("") { "${it.argumentLabel}:" }})"
}

private val KotlinDirectlyCallableMemberSwiftModel.identifierAfterVisibilityChangesWithoutReplaced: String
get() = when (visibility) {
SwiftModelVisibility.Replaced -> identifier
else -> identifierAfterVisibilityChanges
}

fun name(swiftModel: KotlinFunctionSwiftModel): String =
if (swiftModel.valueParameters.isEmpty()) "${swiftModel.identifierAfterVisibilityChanges}()" else reference(swiftModel)

Expand Down

0 comments on commit 44d6166

Please sign in to comment.