Skip to content

Commit

Permalink
Generate Swift Flow bridges using SIR.
Browse files Browse the repository at this point in the history
  • Loading branch information
TadeasKriz committed May 16, 2024
1 parent d3b59e0 commit 776d5d1
Show file tree
Hide file tree
Showing 31 changed files with 1,084 additions and 967 deletions.
2 changes: 1 addition & 1 deletion SKIE/acceptance-tests
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ enum class SupportedFlow(private val directParent: SupportedFlow?) {

val kotlinClassFqName: String

val swiftSimpleName: String

val kind: SupportedFlow

fun getCoroutinesKirClass(kirProvider: KirProvider): KirClass =
Expand All @@ -40,6 +42,9 @@ enum class SupportedFlow(private val directParent: SupportedFlow?) {

fun getSwiftClass(sirProvider: SirProvider): SirClass

context(SirPhase.Context)
fun getCoroutinesKirClass(): KirClass = getCoroutinesKirClass(kirProvider)

context(SirPhase.Context)
fun getKotlinKirClass(): KirClass = getKotlinKirClass(kirProvider)

Expand All @@ -52,11 +57,13 @@ enum class SupportedFlow(private val directParent: SupportedFlow?) {

override val kotlinClassFqName: String = "co.touchlab.skie.runtime.coroutines.flow.SkieKotlin${kind.name}"

override val swiftSimpleName: String = "SkieSwift${kind.name}"

override fun getKotlinKirClass(kirProvider: KirProvider): KirClass =
kirProvider.getClassByFqName(kotlinClassFqName)

override fun getSwiftClass(sirProvider: SirProvider): SirClass =
sirProvider.getClassByFqName(SirFqName(sirProvider.skieModule, "SkieSwift${kind.name}"))
sirProvider.getClassByFqName(SirFqName(sirProvider.skieModule, swiftSimpleName))

override fun isCastableTo(variant: Variant): Boolean {
return kind.isSelfOrChildOf(variant.kind)
Expand All @@ -67,11 +74,13 @@ enum class SupportedFlow(private val directParent: SupportedFlow?) {

override val kotlinClassFqName: String = "co.touchlab.skie.runtime.coroutines.flow.SkieKotlinOptional${kind.name}"

override val swiftSimpleName: String = "SkieSwiftOptional${kind.name}"

override fun getKotlinKirClass(kirProvider: KirProvider): KirClass =
kirProvider.getClassByFqName(kotlinClassFqName)

override fun getSwiftClass(sirProvider: SirProvider): SirClass =
sirProvider.getClassByFqName(SirFqName(sirProvider.skieModule, "SkieSwiftOptional${kind.name}"))
sirProvider.getClassByFqName(SirFqName(sirProvider.skieModule, swiftSimpleName))

override fun isCastableTo(variant: Variant): Boolean {
if (variant is Required) return false
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
package co.touchlab.skie.phases.bridging

import co.touchlab.skie.phases.SirPhase
import co.touchlab.skie.sir.element.*
import co.touchlab.skie.util.swift.addFunctionDeclarationBodyWithErrorTypeHandling
import io.outfoxx.swiftpoet.CodeBlock
import io.outfoxx.swiftpoet.Modifier
import io.outfoxx.swiftpoet.joinToCode

object CustomMembersPassthroughGenerator {
context(SirPhase.Context)
fun generatePassthroughForDeclarations(
targetBridge: SirClass,
declarations: List<CustomPassthroughDeclaration>,
delegateAccessor: CodeBlock,
) {
declarations.forEach {
targetBridge.addPassthroughForDeclaration(it, delegateAccessor)
}
}

context(SirPhase.Context)
private fun SirClass.addPassthroughForDeclaration(declaration: CustomPassthroughDeclaration, delegateAccessor: CodeBlock) {
when (declaration) {
is CustomPassthroughDeclaration.SimpleFunction -> addPassthroughForFunction(declaration, delegateAccessor)
is CustomPassthroughDeclaration.Property -> addPassthroughForProperty(declaration, delegateAccessor)
}
}


context(SirPhase.Context)
private fun SirClass.addPassthroughForFunction(function: CustomPassthroughDeclaration.SimpleFunction, delegateAccessor: CodeBlock) {
SirSimpleFunction(
identifier = function.identifier,
returnType = function.returnType,
visibility = function.visibility,
scope = function.scope,
isAsync = function.isAsync,
throws = function.throws,
).apply {
function.valueParameters.forEach { parameter ->
SirValueParameter(
name = parameter.name,
label = parameter.label,
type = parameter.type,
)
}

addFunctionBody(function, delegateAccessor)
}
}

private fun SirSimpleFunction.addFunctionBody(function: CustomPassthroughDeclaration.SimpleFunction, delegateAccessor: CodeBlock) {
this.addFunctionDeclarationBodyWithErrorTypeHandling(this) {
addCode(
function.transformBody(
CodeBlock.of(
"%L%L%L.%L",
if (function.throws) "try " else "",
if (function.isAsync) "await " else "",
delegateAccessor,
function.call(),
)
)
)
}
}

private fun CustomPassthroughDeclaration.SimpleFunction.call(): CodeBlock {
val argumentsWithLabels = valueParameters.map { parameter ->
if (parameter.label == "_") {
parameter.transformAccess(CodeBlock.of("%L", parameter.name))
} else {
CodeBlock.of(
"%N: %L",
parameter.label ?: parameter.name,
parameter.transformAccess(CodeBlock.of("%L", parameter.name)),
)
}
}.joinToCode(", ")

return CodeBlock.of(
"%L(%L)",
identifier,
argumentsWithLabels,
)
}

context(SirPhase.Context)
private fun SirClass.addPassthroughForProperty(property: CustomPassthroughDeclaration.Property, delegateAccessor: CodeBlock) {
SirProperty(
identifier = property.identifier,
type = property.type,
visibility = property.visibility,
scope = property.scope,
).apply {
addGetter(property, delegateAccessor)
addSetter(property, delegateAccessor)
}
}

private fun SirProperty.addGetter(property: CustomPassthroughDeclaration.Property, delegateAccessor: CodeBlock) {
SirGetter().apply {
this.addFunctionDeclarationBodyWithErrorTypeHandling(this@addGetter) {
addCode(
property.transformGetter(
CodeBlock.of(
"%L.%N",
delegateAccessor,
property.identifier,
)
)
)
}
}
}

private fun SirProperty.addSetter(property: CustomPassthroughDeclaration.Property, delegateAccessor: CodeBlock) {
val setter = property.setter ?: return
val parent = parent

SirSetter(
modifiers = listOfNotNull(Modifier.NONMUTATING.takeIf { parent is SirClass && parent.kind != SirClass.Kind.Class }),
).addSetterBody(property, setter, delegateAccessor)
}

private fun SirSetter.addSetterBody(
property: CustomPassthroughDeclaration.Property,
setter: CustomPassthroughDeclaration.Property.Setter,
delegateAccessor: CodeBlock,
) {
this.addFunctionDeclarationBodyWithErrorTypeHandling(this.property) {
// TODO Remove this once SKIE generates custom header
when (setter) {
CustomPassthroughDeclaration.Property.Setter.MutableProperty -> {
addStatement(
"%L.%N = value",
delegateAccessor,
property.identifier,
)
}
is CustomPassthroughDeclaration.Property.Setter.SimpleFunction -> {
addStatement(
"%L.%N(%L)",
delegateAccessor,
setter.identifier,
setter.parameterLabel?.let {
CodeBlock.of("%N: value", it)
} ?: CodeBlock.of("value"),
)
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package co.touchlab.skie.phases.bridging

import co.touchlab.skie.sir.element.SirScope
import co.touchlab.skie.sir.element.SirVisibility
import co.touchlab.skie.sir.type.SirType
import io.outfoxx.swiftpoet.CodeBlock

sealed interface CustomPassthroughDeclaration {
data class Property(
val identifier: String,
val type: SirType,
val visibility: SirVisibility = SirVisibility.Public,
val scope: SirScope = SirScope.Member,
val transformGetter: (CodeBlock) -> CodeBlock = { it },
val setter: Setter? = null,
): CustomPassthroughDeclaration {
sealed interface Setter {
object MutableProperty: Setter

data class SimpleFunction(
val identifier: String,
val parameterLabel: String? = null,
): Setter
}
}

data class SimpleFunction(
val identifier: String,
val returnType: SirType,
val visibility: SirVisibility = SirVisibility.Public,
val scope: SirScope = SirScope.Member,
val isAsync: Boolean = false,
val throws: Boolean = false,
val valueParameters: List<ValueParameter> = emptyList(),
val transformBody: (CodeBlock) -> CodeBlock = { it },
): CustomPassthroughDeclaration {
data class ValueParameter(
val label: String? = null,
val name: String,
val type: SirType,
val transformAccess: (CodeBlock) -> CodeBlock = { it },
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
package co.touchlab.skie.phases.bridging

import co.touchlab.skie.kir.element.*
import co.touchlab.skie.phases.SirPhase
import co.touchlab.skie.sir.element.*
import co.touchlab.skie.util.swift.addFunctionDeclarationBodyWithErrorTypeHandling
import io.outfoxx.swiftpoet.CodeBlock
import io.outfoxx.swiftpoet.Modifier

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

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

context(SirPhase.Context)
fun generatePassthroughForMembers(
targetBridge: SirClass,
bridgedKirClass: KirClass,
delegateAccessor: CodeBlock,
) {
bridgedKirClass.callableDeclarations
.forEach {
targetBridge.addPassthroughForMember(it, delegateAccessor)
}
}

context(SirPhase.Context)
private fun SirClass.addPassthroughForMember(member: KirCallableDeclaration<*>, delegateAccessor: CodeBlock) {
when (member) {
is KirConstructor -> {
// Constructors do not need passthrough
}
is KirSimpleFunction -> addPassthroughForFunction(member, delegateAccessor)
is KirProperty -> addPassthroughForProperty(member, delegateAccessor)
}
}

context(SirPhase.Context)
private fun SirClass.addPassthroughForFunction(function: KirSimpleFunction, delegateAccessor: CodeBlock) {
function.forEachAssociatedExportedSirDeclaration {
addPassthroughForFunction(it, delegateAccessor)
}
}

private fun SirClass.addPassthroughForFunction(function: SirSimpleFunction, delegateAccessor: CodeBlock) {
if (!function.isSupported) {
return
}

function.shallowCopy(parent = this, isFakeOverride = false).apply {
copyValueParametersFrom(function)

addFunctionBody(function, delegateAccessor)
}
}

private fun SirSimpleFunction.addFunctionBody(function: SirSimpleFunction, delegateAccessor: CodeBlock) {
this.addFunctionDeclarationBodyWithErrorTypeHandling(function) {
addStatement(
"return %L%L%L.%L",
if (function.throws) "try " else "",
if (function.isAsync) "await " else "",
delegateAccessor,
function.call(function.valueParameters),
)
}
}

context(SirPhase.Context)
private fun SirClass.addPassthroughForProperty(property: KirProperty, delegateAccessor: CodeBlock) {
property.forEachAssociatedExportedSirDeclaration {
addPassthroughForProperty(it, delegateAccessor)
}
}

private fun SirClass.addPassthroughForProperty(property: SirProperty, delegateAccessor: CodeBlock) {
property.shallowCopy(
parent = this,
isFakeOverride = false,
).apply {
addGetter(property, delegateAccessor)
addSetter(property, delegateAccessor)
}
}

private fun SirProperty.addGetter(property: SirProperty, delegateAccessor: CodeBlock) {
val getter = property.getter ?: return

SirGetter(
throws = getter.throws,
).addGetterBody(property, getter, delegateAccessor)
}

private fun SirGetter.addGetterBody(property: SirProperty, getter: SirGetter, delegateAccessor: CodeBlock) {
this.addFunctionDeclarationBodyWithErrorTypeHandling(property) {
addStatement(
"return %L%L.%N",
if (getter.throws) "try " else "",
delegateAccessor,
property.reference,
)
}
}

private fun SirProperty.addSetter(property: SirProperty, delegateAccessor: CodeBlock) {
val setter = property.setter ?: return
val parent = parent

SirSetter(
throws = setter.throws,
modifiers = listOfNotNull(Modifier.NONMUTATING.takeIf { parent is SirClass && parent.kind != SirClass.Kind.Class }),
).addSetterBody(property, setter, delegateAccessor)
}

private fun SirSetter.addSetterBody(
property: SirProperty,
setter: SirSetter,
delegateAccessor: CodeBlock,
) {
this.addFunctionDeclarationBodyWithErrorTypeHandling(property) {
// TODO Remove this filter once SKIE generates custom header
val isParentProtocol = (property.parent as? SirClass)?.kind == SirClass.Kind.Protocol
if (property.isOverriddenFromReadOnlyProperty && isParentProtocol) {
addStatement(
"%L%L.%N(value)",
if (setter.throws) "try " else "",
delegateAccessor,
"set" + property.reference.replaceFirstChar { it.uppercase() },
)
} else {
addStatement(
"%L%L.%N = value",
if (setter.throws) "try " else "",
delegateAccessor,
property.reference,
)
}
}
}
}
Loading

0 comments on commit 776d5d1

Please sign in to comment.