Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial support for Swift export #723

Open
wants to merge 5 commits into
base: 2.0.0-beta2-prepare
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ dependencies {
implementation("org.jetbrains.kotlin:core:231-$kotlinIdeVersion-$kotlinIdeVersionSuffix")
implementation(project(":executors", configuration = "default"))
implementation(project(":common", configuration = "default"))
implementation(project(":swift-export-playground", configuration = "default"))

testImplementation("org.springframework.boot:spring-boot-starter-test") {
exclude(group = "org.junit.vintage", module = "junit-vintage-engine")
Expand Down
9 changes: 5 additions & 4 deletions common/src/main/kotlin/component/KotlinEnvironment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import org.jetbrains.kotlin.serialization.js.JsModuleDescriptor
import org.jetbrains.kotlin.serialization.js.KotlinJavascriptSerializationUtil
import org.jetbrains.kotlin.serialization.js.ModuleKind
import org.jetbrains.kotlin.utils.KotlinJavascriptMetadataUtils
import org.jetbrains.kotlin.wasm.config.WasmConfigurationKeys
import java.io.File

class KotlinEnvironment(
Expand Down Expand Up @@ -89,15 +90,15 @@ class KotlinEnvironment(
val wasmConfiguration: CompilerConfiguration = configuration.copy().apply {
put(CommonConfigurationKeys.MODULE_NAME, "moduleId")
put(JSConfigurationKeys.LIBRARIES, WASM_LIBRARIES)
put(JSConfigurationKeys.WASM_ENABLE_ARRAY_RANGE_CHECKS, false)
put(JSConfigurationKeys.WASM_ENABLE_ASSERTS, false)
put(WasmConfigurationKeys.WASM_ENABLE_ARRAY_RANGE_CHECKS, false)
put(WasmConfigurationKeys.WASM_ENABLE_ASSERTS, false)
}

val composeWasmConfiguration: CompilerConfiguration = configuration.copy().apply {
put(CommonConfigurationKeys.MODULE_NAME, "moduleId")
put(JSConfigurationKeys.LIBRARIES, COMPOSE_WASM_LIBRARIES)
put(JSConfigurationKeys.WASM_ENABLE_ARRAY_RANGE_CHECKS, false)
put(JSConfigurationKeys.WASM_ENABLE_ASSERTS, false)
put(WasmConfigurationKeys.WASM_ENABLE_ARRAY_RANGE_CHECKS, false)
put(WasmConfigurationKeys.WASM_ENABLE_ASSERTS, false)

PluginCliParser.loadPluginsSafe(
COMPOSE_WASM_COMPILER_PLUGINS,
Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
systemProp.kotlinVersion=2.0.0
systemProp.kotlinVersion=2.1.0-dev-3023
systemProp.kotlinIdeVersion=1.9.20-506
systemProp.kotlinIdeVersionSuffix=IJ8109.175
systemProp.policy=executor.policy
Expand Down
2 changes: 2 additions & 0 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@ include(":executors")
include(":indexation")
include(":common")
include(":dependencies")
include(":swift-export-playground")

pluginManagement {
repositories {
gradlePluginPortal()
mavenCentral()
maven("https://maven.pkg.jetbrains.space/kotlin/p/kotlin/bootstrap")
maven("https://maven.pkg.jetbrains.space/kotlin/p/kotlin/dev")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import com.compiler.server.model.ErrorDescriptor
import com.compiler.server.model.ProjectSeveriry
import com.compiler.server.model.TextInterval
import org.jetbrains.kotlin.cli.common.CLICompiler
import org.jetbrains.kotlin.cli.common.CLITool
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity.*
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSourceLocation
Expand Down Expand Up @@ -56,7 +55,7 @@ fun <T> CLICompiler<*>.tryCompilation(inputDirectory: Path, inputFiles: List<Pat
inputFiles.forEach { put(it.outputFilePathString(), mutableListOf()) }
}
val defaultFileName = inputFiles.singleOrNull()?.outputFilePathString() ?: ""
val exitCode = CLITool.doMainNoExit(this, arguments.toTypedArray(), object : MessageRenderer {
val exitCode = CLICompiler.doMainNoExit(this, arguments.toTypedArray(), object : MessageRenderer {
override fun renderPreamble(): String = ""

override fun render(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.compiler.server.compiler.components

import com.compiler.server.model.CompilerDiagnostics
import com.compiler.server.model.SwiftExportResult
import com.compiler.server.model.toExceptionDescriptor
import component.KotlinEnvironment
import org.jetbrains.kotlin.psi.KtFile
import org.springframework.stereotype.Component
import runSwiftExport
import java.nio.file.Path

@Component
class SwiftExportTranslator(
private val kotlinEnvironment: KotlinEnvironment,
) {
fun translate(files: List<KtFile>): SwiftExportResult = try {
usingTempDirectory { tempDirectory ->
val ioFiles = files.writeToIoFiles(tempDirectory)
val stdlib = kotlinEnvironment.WASM_LIBRARIES.singleOrNull { "stdlib" in it }
val swiftCode = runSwiftExport(
sourceFile = ioFiles.first(),
stdlibPath = stdlib?.let { Path.of(it) },
)
SwiftExportResult(
compilerDiagnostics = CompilerDiagnostics(emptyMap()),
swiftCode = swiftCode
)
}
} catch (e: Exception) {
SwiftExportResult(swiftCode = "", exception = e.toExceptionDescriptor())
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ class CompilerRestController(private val kotlinProjectExecutor: KotlinProjectExe
KotlinTranslatableCompiler.JS -> kotlinProjectExecutor.convertToJsIr(project)
KotlinTranslatableCompiler.WASM -> kotlinProjectExecutor.convertToWasm(project, debugInfo)
KotlinTranslatableCompiler.COMPOSE_WASM -> kotlinProjectExecutor.convertToWasm(project, debugInfo)
KotlinTranslatableCompiler.SWIFT_EXPORT -> kotlinProjectExecutor.convertToSwift(project).let {
// TODO: A hack to avoid changing the return type of the function.
object : TranslationResultWithJsCode(it.swiftCode, it.compilerDiagnostics, it.exception) {}
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ class KotlinPlaygroundRestController(private val kotlinProjectExecutor: KotlinPr
debugInfo = false,
)
ProjectType.JUNIT -> kotlinProjectExecutor.test(project, addByteCode)
ProjectType.SWIFT_EXPORT -> kotlinProjectExecutor.convertToSwift(project)
}
}

Expand Down
8 changes: 8 additions & 0 deletions src/main/kotlin/com/compiler/server/model/ExecutionResult.kt
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,14 @@ class JunitExecutionResult(
jvmBytecode: String? = null,
) : JvmExecutionResult(compilerDiagnostics, exception, jvmBytecode)

class SwiftExportResult(
val swiftCode: String,
override var exception: ExceptionDescriptor? = null,
@field:JsonProperty("errors")
override var compilerDiagnostics: CompilerDiagnostics = CompilerDiagnostics()
) : ExecutionResult(compilerDiagnostics, exception)


private fun unEscapeOutput(value: String) = value.replace("&amp;lt;".toRegex(), "<")
.replace("&amp;gt;".toRegex(), ">")
.replace("\r", "")
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@ package com.compiler.server.model
enum class KotlinTranslatableCompiler {
JS,
WASM,
COMPOSE_WASM
COMPOSE_WASM,
SWIFT_EXPORT,
}
4 changes: 3 additions & 1 deletion src/main/kotlin/com/compiler/server/model/Project.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ enum class ProjectType(@JsonValue val id: String) {
CANVAS("canvas"),
JS_IR("js-ir"),
WASM("wasm"),
COMPOSE_WASM("compose-wasm");
COMPOSE_WASM("compose-wasm"),
SWIFT_EXPORT("swift-export")
;

fun isJvmRelated(): Boolean = this == JAVA || this == JUNIT

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ class KotlinProjectExecutor(
private val completionProvider: CompletionProvider,
private val version: VersionInfo,
private val kotlinToJSTranslator: KotlinToJSTranslator,
private val swiftExportTranslator: SwiftExportTranslator,
private val kotlinEnvironment: KotlinEnvironment,
private val loggerDetailsStreamer: LoggerDetailsStreamer? = null,
) {
Expand Down Expand Up @@ -52,6 +53,10 @@ class KotlinProjectExecutor(
return convertWasmWithConverter(project, debugInfo, kotlinToJSTranslator::doTranslateWithWasm)
}

fun convertToSwift(project: Project): SwiftExportResult {
return convertSwiftWithConverter(project)
}

fun complete(project: Project, line: Int, character: Int): List<Completion> {
return kotlinEnvironment.environment {
val file = getFilesFrom(project, it).first()
Expand All @@ -76,6 +81,7 @@ class KotlinProjectExecutor(
project,
debugInfo = false,
).compilerDiagnostics
ProjectType.SWIFT_EXPORT -> convertToSwift(project).compilerDiagnostics
}
} catch (e: Exception) {
log.warn("Exception in getting highlight. Project: $project", e)
Expand Down Expand Up @@ -114,6 +120,15 @@ class KotlinProjectExecutor(
}.also { logExecutionResult(project, it) }
}

private fun convertSwiftWithConverter(
project: Project,
): SwiftExportResult {
return kotlinEnvironment.environment { environment ->
val files = getFilesFrom(project, environment).map { it.kotlinFile }
swiftExportTranslator.translate(files)
}.also { logExecutionResult(project, it) }
}

private fun logExecutionResult(project: Project, executionResult: ExecutionResult) {
loggerDetailsStreamer?.logExecutionResult(
executionResult,
Expand Down
110 changes: 110 additions & 0 deletions src/test/kotlin/com/compiler/server/SwiftConverterTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package com.compiler.server

import com.compiler.server.base.BaseExecutorTest
import org.junit.jupiter.api.Test
import kotlin.test.assertContains
import kotlin.test.assertEquals

class SwiftConverterTest : BaseExecutorTest() {

private fun exactTest(input: String, expected: String) {
val actual = translateToSwift(input)
assertEquals(expected, actual.swiftCode.trimEnd())
}

private fun containsTest(input: String, expected: String) {
val actual = translateToSwift(input)
assertContains(actual.swiftCode.trimEnd(), expected)
}

@Test
fun basicSwiftExportTest() = containsTest(
input = """
fun main() {}
""".trimIndent(),
expected = "public func main() -> Swift.Void"
)

@Test
fun `use stdlib declaration`() = containsTest(
input = "fun foo(): UInt = 42",
expected = """
public func foo() -> Swift.UInt32 {
stub()
}
""".trimIndent()
)

@Test
fun `class declaration`() = exactTest(
input = "public class MyClass { public fun A() {}}",
expected = """
import KotlinRuntime

public class MyClass : KotlinRuntime.KotlinBase {
public override init() {
stub()
}
public override init(
__externalRCRef: Swift.UInt
) {
stub()
}
public func A() -> Swift.Void {
stub()
}
}
""".trimIndent()
)

@Test
fun `simple packages`() = exactTest(
input = """
package foo.bar

val myProperty: Int = 42
""".trimIndent(),
expected = """
@_exported import pkg

public extension pkg.foo.bar {
public static var myProperty: Swift.Int32 {
get {
stub()
}
}
}
""".trimIndent()
)

@Test
fun `invalid code`() = exactTest(
input = "abracadabra",
expected = """
""".trimIndent()
)

@Test
fun `more invalid code`() = exactTest(
input = "fun foo(): Bar = error()",
expected = """
public func foo() -> ERROR_TYPE {
stub()
}
""".trimIndent()
)

@Test
fun `unsupported type declaration`() = exactTest(
input = """
interface Foo

fun produceFoo(): Foo = TODO()
""".trimIndent(),
expected = """
public func produceFoo() -> Swift.Never {
stub()
}
""".trimIndent()
)
}
2 changes: 2 additions & 0 deletions src/test/kotlin/com/compiler/server/base/BaseExecutorTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ class BaseExecutorTest {

fun translateToJsIr(@Language("kotlin") code: String) = testRunner.translateToJsIr(code)

fun translateToSwift(code: String) = testRunner.translateToSwift(code)

fun runWithException(@Language("kotlin") code: String, contains: String, message: String? = null, addByteCode: Boolean = false) =
testRunner.runWithException(code, contains, message, addByteCode)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,11 @@ class TestProjectRunner {
)
}

fun translateToSwift(code: String): SwiftExportResult {
val project = generateSingleProject(text = code, projectType = ProjectType.SWIFT_EXPORT)
return kotlinProjectExecutor.convertToSwift(project)
}

fun runWithException(
@Language("kotlin")
code: String,
Expand Down
1 change: 1 addition & 0 deletions swift-export-playground/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
An implementation of Swift export for Kotlin Playground.
35 changes: 35 additions & 0 deletions swift-export-playground/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
plugins {
kotlin("jvm")
}

repositories {
mavenCentral()
// For Analysis API components
maven("https://maven.pkg.jetbrains.space/kotlin/p/kotlin/kotlin-ide-plugin-dependencies")
maven("https://maven.pkg.jetbrains.space/kotlin/p/kotlin/dev")
}

val kotlinVersion = rootProject.properties["systemProp.kotlinVersion"]
val aaVersion = "2.1.0-dev-2515"

dependencies {
implementation("org.jetbrains.kotlin:kotlin-compiler:$aaVersion")

// Analysis API components which are required for the Swift export
implementation("org.jetbrains.kotlin:analysis-api-standalone-for-ide:$aaVersion") { isTransitive = false }
implementation("org.jetbrains.kotlin:high-level-api-for-ide:$aaVersion") { isTransitive = false }
implementation("org.jetbrains.kotlin:high-level-api-fir-for-ide:$aaVersion") { isTransitive = false }
implementation("org.jetbrains.kotlin:high-level-api-impl-base-for-ide:$aaVersion") { isTransitive = false }
implementation("org.jetbrains.kotlin:low-level-api-fir-for-ide:$aaVersion") { isTransitive = false }
implementation("org.jetbrains.kotlin:symbol-light-classes-for-ide:$aaVersion") { isTransitive = false }
implementation("org.jetbrains.kotlin:analysis-api-platform-interface-for-ide:$aaVersion") { isTransitive = false }

// Swift export not-yet-published dependencies.
implementation("org.jetbrains.kotlin:sir:$kotlinVersion") { isTransitive = false }
implementation("org.jetbrains.kotlin:sir-providers:$kotlinVersion") { isTransitive = false }
implementation("org.jetbrains.kotlin:sir-light-classes:$kotlinVersion") { isTransitive = false }
implementation("org.jetbrains.kotlin:sir-printer:$kotlinVersion") { isTransitive = false }

testImplementation("junit:junit:4.13.2")
testImplementation("org.jetbrains.kotlin:kotlin-test:$kotlinVersion")
}
Loading