Skip to content

Commit

Permalink
Fixes kotlin to swift type convertion and adds generics
Browse files Browse the repository at this point in the history
  • Loading branch information
GuilhE committed Nov 5, 2024
1 parent c913b95 commit ac483ae
Show file tree
Hide file tree
Showing 4 changed files with 109 additions and 64 deletions.
22 changes: 11 additions & 11 deletions .idea/caches/deviceStreaming.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,7 @@ internal class Processor(
val frameworks = frameworkBaseName.joinToString("\n") { "import ${it.name()}" }
val makeParametersParsed = makeParameters.joinToString(", ") { "${it.name()}: ${it.name()}" }
val letParameters = makeParameters.joinToString("\n") {
val type = kotlinTypeToSwift(it)
val type = it.resolveType(toSwift = true)
val finalType = if (externalParameters.containsKey(type)) {
externalParameters[type]
} else type
Expand Down Expand Up @@ -322,7 +322,7 @@ internal class Processor(
val frameworks = frameworkBaseName.joinToString("\n") { "import ${it.name()}" }
val makeParametersParsed = makeParameters.joinToString(", ") { "${it.name()}: ${it.name()}" }
val letParameters = makeParameters.joinToString("\n") {
val type = kotlinTypeToSwift(it)
val type = it.resolveType(toSwift = true)
val finalType = externalParameters[type] ?: type
"let ${it.name()}: $finalType"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,38 +4,72 @@ import com.github.guilhe.kmp.composeuiviewcontroller.common.FILE_NAME_ARGS
import com.github.guilhe.kmp.composeuiviewcontroller.common.ModuleMetadata
import com.google.devtools.ksp.symbol.KSClassDeclaration
import com.google.devtools.ksp.symbol.KSFunctionDeclaration
import com.google.devtools.ksp.symbol.KSTypeReference
import com.google.devtools.ksp.symbol.KSType
import com.google.devtools.ksp.symbol.KSValueParameter

/**
* Transforms Kotlin types into their Swift representation.
* [Apple framework generated framework headers](https://kotlinlang.org/docs/apple-framework.html#generated-framework-headers)
*
* @param type [KSTypeReference] to be converted to Swift type
* @return String with Swift type
* Resolves KSValueParameter type
* @param toSwift If true, transforms Kotlin types into their Swift representation. [Apple framework generated framework headers](https://kotlinlang.org/docs/apple-framework.html#generated-framework-headers)
* @return String with type resolved
* @throws TypeResolutionError when type cannot be resolved
*/
internal fun kotlinTypeToSwift(type: KSValueParameter): String {
val regex = "\\b(Unit|List|MutableList|Map|MutableMap|Byte|UByte|Short|UShort|Int|UInt|Long|ULong|Float|Double|Boolean)\\b".toRegex()
return regex.replace(type.resolveType()) { matchResult ->
when (matchResult.value) {
"Unit" -> "Void"
"List" -> "Array"
"MutableList" -> "NSMutableArray"
"Map" -> "Dictionary"
"MutableMap" -> "NSMutableDictionary"
"Byte" -> "KotlinByte"
"UByte" -> "KotlinUByte"
"Short" -> "KotlinShort"
"UShort" -> "KotlinUShort"
"Int" -> "KotlinInt"
"UInt" -> "KotlinUInt"
"Long" -> "KotlinLong"
"ULong" -> "KotlinULong"
"Float" -> "KotlinFloat"
"Double" -> "KotlinDouble"
"Boolean" -> "KotlinBoolean"
else -> "KotlinNumber"
internal fun KSValueParameter.resolveType(toSwift: Boolean = false): String {
//println(">> KSValueParameter type: ${type}")
val resolvedType = type.resolve()
return if (resolvedType.isFunctionType) {
buildString {
append("(")
append(resolvedType.arguments.dropLast(1).joinToString(", ") { arg ->
val argType = arg.type?.resolve()
if (argType == null || argType.isError) {
throw TypeResolutionException(resolvedType)
} else {
convertGenericType(argType, toSwift)
}
})
append(") -> ")
val returnType = resolvedType.arguments.last().type?.resolve()
val returnTypeName = if (returnType == null || returnType.isError) {
throw TypeResolutionException(resolvedType)
} else {
convertGenericType(returnType, toSwift)
}
append(returnTypeName)
}
} else {
convertGenericType(resolvedType, toSwift)
}
}

private fun convertGenericType(type: KSType, toSwift: Boolean): String {
val baseType = type.declaration.simpleName.asString()
val convertedBaseType = if (toSwift) convertToSwift(baseType) else baseType
if (type.arguments.isEmpty()) return convertedBaseType
val generics = type.arguments.joinToString(", ") { arg ->
arg.type?.resolve()?.let { convertGenericType(it, toSwift) } ?: "Unknown"
}
return "$convertedBaseType<$generics>"
}

private fun convertToSwift(baseType: String): String {
return when (baseType) {
"Unit" -> "Void"
"List" -> "Array"
"MutableList" -> "NSMutableArray"
"Map" -> "Dictionary"
"MutableMap" -> "NSMutableDictionary"
"Byte" -> "KotlinByte"
"UByte" -> "KotlinUByte"
"Short" -> "KotlinShort"
"UShort" -> "KotlinUShort"
"Int" -> "KotlinInt"
"UInt" -> "KotlinUInt"
"Long" -> "KotlinLong"
"ULong" -> "KotlinULong"
"Float" -> "KotlinFloat"
"Double" -> "KotlinDouble"
"Boolean" -> "KotlinBoolean"
else -> baseType
}
}

Expand Down Expand Up @@ -151,23 +185,6 @@ internal fun List<KSValueParameter>.joinToStringDeclaration(separator: CharSeque
"${it.name!!.getShortName()}: ${it.resolveType()}"
}

internal fun KSValueParameter.resolveType(): String {
//println(">> KSValueParameter type: ${type}")
val resolvedType = type.resolve()
return if (resolvedType.isFunctionType) {
buildString {
append("(")
append(resolvedType.arguments.dropLast(1).joinToString(", ") { arg ->
arg.type?.resolve()?.declaration?.simpleName?.asString() ?: "Unknown"
})
append(") -> ")
append(resolvedType.arguments.last().type?.resolve()?.declaration?.simpleName?.asString() ?: "Unit")
}
} else {
resolvedType.declaration.simpleName.asString()
}
}

internal fun KSFunctionDeclaration.name(): String = qualifiedName!!.getShortName()

internal fun KSValueParameter.name(): String = name!!.getShortName()
Expand All @@ -189,4 +206,7 @@ internal class TypeResolutionError(parameter: KSValueParameter) : IllegalArgumen
"Cannot resolve type for parameter ${parameter.name()} from ${parameter.location}. Check your file imports"
)

internal class ModuleDecodeException(e: Exception) : IllegalArgumentException("Could not decode $FILE_NAME_ARGS file with exception: ${e.localizedMessage}")
internal class ModuleDecodeException(e: Exception) :
IllegalArgumentException("Could not decode $FILE_NAME_ARGS file with exception: ${e.localizedMessage}")

internal class TypeResolutionException(type: KSType) : IllegalArgumentException("Could not resolve function parameter: ${type}")
Original file line number Diff line number Diff line change
Expand Up @@ -439,10 +439,10 @@ class ProcessorTest {
fun Screen(
@ComposeUIViewControllerState state: ViewState,
callBackA: () -> Unit,
callBackB: (List) -> Unit,
callBackC: (MutableList) -> Unit,
callBackD: (Map) -> Unit,
callBackE: (MutableMap) -> Unit,
callBackB: (List<String>) -> Unit,
callBackC: (MutableList<String>) -> Unit,
callBackD: (Map<String, String>) -> Unit,
callBackE: (MutableMap<String, String>) -> Unit,
callBackF: (Byte) -> Unit,
callBackG: (UByte) -> Unit,
callBackH: (Short) -> Unit,
Expand All @@ -467,9 +467,9 @@ class ProcessorTest {

val expectedSwiftTypes = listOf(
"Void",
"Array",
"Array<String>",
"NSMutableArray",
"Dictionary",
"Dictionary<String, String>",
"NSMutableDictionary",
"KotlinByte",
"KotlinUByte",
Expand All @@ -489,6 +489,31 @@ class ProcessorTest {
}
}

@Test
fun `Function parameter with List, MutableList, Map or MutableMap without type specification will throw TypeResolutionError`() {
val code = """
package com.mycomposable.test
import $composeUIViewControllerAnnotationName
import $composeUIViewControllerStateAnnotationName
import com.mycomposable.data.ViewState
private data class ViewState(val field: Int)
@ComposeUIViewController("MyFramework")
@Composable
fun Screen(
@ComposeUIViewControllerState state: ViewState,
callBackB: (List) -> Unit,
callBackC: (MutableList) -> Unit,
callBackD: (Map) -> Unit,
callBackE: (MutableMap) -> Unit,
) { }
""".trimIndent()
val compilation = prepareCompilation(kotlin("Screen.kt", code))
val result = compilation.compile()
assertEquals(result.exitCode, KotlinCompilation.ExitCode.COMPILATION_ERROR)
}

@Test
fun `Types imported from different KMP modules will not produce Swift files by default`() {
val data = """
Expand Down

0 comments on commit ac483ae

Please sign in to comment.