-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Generate Swift Flow bridges using SIR.
- Loading branch information
1 parent
d3b59e0
commit 776d5d1
Showing
31 changed files
with
1,084 additions
and
967 deletions.
There are no files selected for viewing
Submodule acceptance-tests
updated
from 93a96f to 7bc9c7
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
155 changes: 155 additions & 0 deletions
155
...c/commonMain/kotlin/co/touchlab/skie/phases/bridging/CustomMembersPassthroughGenerator.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"), | ||
) | ||
} | ||
} | ||
} | ||
} | ||
} |
44 changes: 44 additions & 0 deletions
44
...re/src/commonMain/kotlin/co/touchlab/skie/phases/bridging/CustomPassthroughDeclaration.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 }, | ||
) | ||
} | ||
} |
141 changes: 141 additions & 0 deletions
141
...c/commonMain/kotlin/co/touchlab/skie/phases/bridging/DirectMembersPassthroughGenerator.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | ||
) | ||
} | ||
} | ||
} | ||
} |
Oops, something went wrong.