Skip to content

Commit

Permalink
Basic Swift export
Browse files Browse the repository at this point in the history
Minor hack: use wasm stdlib to avoid downloading the whole K/N distribution
  • Loading branch information
sbogolepov committed May 7, 2024
1 parent 50e6205 commit 9a58408
Show file tree
Hide file tree
Showing 18 changed files with 405 additions and 2 deletions.
2 changes: 2 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ allprojects {
maven("https://maven.pkg.jetbrains.space/kotlin/p/kotlin/bootstrap")
maven("https://maven.pkg.jetbrains.space/kotlin/p/wasm/experimental")
maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
maven("https://maven.pkg.jetbrains.space/kotlin/p/kotlin/swift-export-experimental")
}
afterEvaluate {
dependencies {
Expand Down Expand Up @@ -74,6 +75,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
1 change: 1 addition & 0 deletions gradle.properties
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
systemProp.kotlinVersion=2.0.0-RC2
systemProp.kotlinIdeVersion=1.9.20-506
systemProp.kotlinIdeVersionSuffix=IJ8109.175
systemProp.swiftExportVersion=2.0.20-dev-3080
systemProp.policy=executor.policy
systemProp.indexes=indexes.json
systemProp.indexesJs=indexesJs.json
Expand Down
1 change: 1 addition & 0 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ include(":executors")
include(":indexation")
include(":common")
include(":dependencies")
include(":swift-export-playground")

pluginManagement {
repositories {
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 @@ -28,6 +28,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 -> 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 @@ -50,6 +50,7 @@ class KotlinPlaygroundRestController(private val kotlinProjectExecutor: KotlinPr
debugInfo = false,
)
ProjectType.JUNIT -> kotlinProjectExecutor.test(project)
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 @@ -82,6 +82,14 @@ class JunitExecutionResult(
override var compilerDiagnostics: CompilerDiagnostics = CompilerDiagnostics()
) : ExecutionResult(compilerDiagnostics, exception)

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,
}
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
99 changes: 99 additions & 0 deletions src/test/kotlin/com/compiler/server/SwiftConverterTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package com.compiler.server

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

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)
}

private fun shouldFailTest(input: String) {
val actual = translateToSwift(input)
assertTrue(actual.hasErrors())
}

@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 {
fatalError()
}
""".trimIndent()
)

@Test
fun `class declaration`() = exactTest(
input = "public class MyClass { public fun A() {}}",
expected = """
import KotlinRuntime
public class MyClass : KotlinRuntime.KotlinBase {
public override init() {
fatalError()
}
public func A() -> Swift.Void {
fatalError()
}
}
""".trimIndent()
)

@Test
fun `simple packages`() = exactTest(
input = """
package foo.bar
val myProperty: Int = 42
""".trimIndent(),
expected = """
public extension Playground.foo.bar {
public static var myProperty: Swift.Int32 {
get {
fatalError()
}
}
}
public enum foo {
public enum bar {
}
}
""".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 {
fatalError()
}
""".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 @@ -60,6 +60,8 @@ class BaseExecutorTest {

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

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

fun runWithException(code: String, contains: String, message: String? = null) = testRunner.runWithException(code, contains, message)

fun version() = testRunner.getVersion()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,11 @@ class TestProjectRunner {
)
}

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

fun runWithException(code: String, contains: String, message: String? = null): ExecutionResult {
val project = generateSingleProject(text = code)
val result = kotlinProjectExecutor.run(project)
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.
36 changes: 36 additions & 0 deletions swift-export-playground/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
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/swift-export-experimental")
}

val kotlinVersion = rootProject.properties["systemProp.kotlinVersion"]
val swiftExportVersion = rootProject.properties["systemProp.swiftExportVersion"]

dependencies {
implementation("org.jetbrains.kotlin:kotlin-compiler:$kotlinVersion")
// For K/N Distribution class
implementation("org.jetbrains.kotlin:kotlin-native-utils:$kotlinVersion")

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

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

testImplementation("junit:junit:4.13.2")
testImplementation("org.jetbrains.kotlin:kotlin-test:$kotlinVersion")
}
31 changes: 31 additions & 0 deletions swift-export-playground/src/main/kotlin/PlaygroundSirSession.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import org.jetbrains.kotlin.analysis.project.structure.KtModule
import org.jetbrains.kotlin.sir.providers.SirSession
import org.jetbrains.kotlin.sir.providers.SirTypeProvider
import org.jetbrains.kotlin.sir.providers.impl.*
import org.jetbrains.sir.lightclasses.SirDeclarationFromKtSymbolProvider

internal class PlaygroundSirSession(
ktModule: KtModule,
) : SirSession {
override val declarationNamer = SirDeclarationNamerImpl()
override val enumGenerator = SirEnumGeneratorImpl()
override val moduleProvider = SirSingleModuleProvider("Playground")
override val declarationProvider = CachingSirDeclarationProvider(
declarationsProvider = SirDeclarationFromKtSymbolProvider(
ktModule = ktModule,
sirSession = sirSession,
)
)
override val parentProvider = SirParentProviderImpl(
sirSession = sirSession,
)
override val typeProvider = SirTypeProviderImpl(
errorTypeStrategy = SirTypeProvider.ErrorTypeStrategy.ErrorType,
unsupportedTypeStrategy = SirTypeProvider.ErrorTypeStrategy.ErrorType,
sirSession = sirSession,
)
override val visibilityChecker = SirVisibilityCheckerImpl()
override val childrenProvider = SirDeclarationChildrenProviderImpl(
sirSession = sirSession,
)
}
Loading

0 comments on commit 9a58408

Please sign in to comment.