Skip to content

Commit d331d72

Browse files
committed
Basic Swift export support without stdlib
1 parent d25a530 commit d331d72

File tree

18 files changed

+366
-2
lines changed

18 files changed

+366
-2
lines changed

build.gradle.kts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ allprojects {
9191
maven("https://www.myget.org/F/rd-snapshots/maven/")
9292
maven("https://kotlin.jetbrains.space/p/kotlin/packages/maven/kotlin-ide")
9393
maven("https://maven.pkg.jetbrains.space/kotlin/p/kotlin/bootstrap")
94+
maven("https://maven.pkg.jetbrains.space/kotlin/p/kotlin/swift-export-experimental")
9495
}
9596
afterEvaluate {
9697
dependencies {
@@ -141,6 +142,7 @@ dependencies {
141142
implementation("org.jetbrains.kotlin:core:231-$kotlinIdeVersion-$kotlinIdeVersionSuffix")
142143
implementation(project(":executors", configuration = "default"))
143144
implementation(project(":common", configuration = "default"))
145+
implementation(project(":swift-export-playground", configuration = "default"))
144146

145147
testImplementation("org.springframework.boot:spring-boot-starter-test") {
146148
exclude(group = "org.junit.vintage", module = "junit-vintage-engine")

gradle.properties

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
systemProp.kotlinVersion=2.0.0-RC1
22
systemProp.kotlinIdeVersion=1.9.20-506
33
systemProp.kotlinIdeVersionSuffix=IJ8109.175
4+
systemProp.swiftExportVersion=2.0.20-dev-1552
45
systemProp.policy=executor.policy
56
systemProp.indexes=indexes.json
67
systemProp.indexesJs=indexesJs.json

settings.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ rootProject.name = "kotlin-compiler-server"
22
include(":executors")
33
include(":indexation")
44
include(":common")
5+
include(":swift-export-playground")
56

67
pluginManagement {
78
repositories {
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package com.compiler.server.compiler.components
2+
3+
import com.compiler.server.model.CompilerDiagnostics
4+
import com.compiler.server.model.SwiftExportResult
5+
import com.compiler.server.model.toExceptionDescriptor
6+
import component.KotlinEnvironment
7+
import org.jetbrains.kotlin.psi.KtFile
8+
import org.springframework.stereotype.Component
9+
import runSwiftExport
10+
import java.nio.file.Path
11+
12+
@Component
13+
class SwiftExportTranslator(
14+
private val kotlinEnvironment: KotlinEnvironment,
15+
) {
16+
fun translate(files: List<KtFile>): SwiftExportResult = try {
17+
usingTempDirectory { tempDirectory ->
18+
val ioFiles = files.writeToIoFiles(tempDirectory)
19+
val stdlib = kotlinEnvironment.WASM_LIBRARIES.singleOrNull { "stdlib" in it }
20+
val swiftCode = runSwiftExport(
21+
sourceFile = ioFiles.first(),
22+
stdlibPath = stdlib?.let { Path.of(it) },
23+
)
24+
SwiftExportResult(
25+
compilerDiagnostics = CompilerDiagnostics(emptyMap()),
26+
swiftCode = swiftCode
27+
)
28+
}
29+
} catch (e: Exception) {
30+
SwiftExportResult(swiftCode = "", exception = e.toExceptionDescriptor())
31+
}
32+
}

src/main/kotlin/com/compiler/server/controllers/CompilerRestController.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ class CompilerRestController(private val kotlinProjectExecutor: KotlinProjectExe
2626
return when (KotlinTranslatableCompiler.valueOf(compiler.uppercase())) {
2727
KotlinTranslatableCompiler.JS -> kotlinProjectExecutor.convertToJsIr(project)
2828
KotlinTranslatableCompiler.WASM -> kotlinProjectExecutor.convertToWasm(project)
29+
KotlinTranslatableCompiler.SWIFT -> kotlinProjectExecutor.convertToSwift(project).let {
30+
// TODO: A hack to avoid changing the return type of the function.
31+
object : TranslationResultWithJsCode(it.swiftCode, it.compilerDiagnostics, it.exception) {}
32+
}
2933
}
3034
}
3135

src/main/kotlin/com/compiler/server/controllers/KotlinPlaygroundRestController.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ class KotlinPlaygroundRestController(private val kotlinProjectExecutor: KotlinPr
4444
ProjectType.JS_IR, ProjectType.CANVAS -> kotlinProjectExecutor.convertToJsIr(project)
4545
ProjectType.WASM -> kotlinProjectExecutor.convertToWasm(project)
4646
ProjectType.JUNIT -> kotlinProjectExecutor.test(project)
47+
ProjectType.SWIFT_EXPORT -> kotlinProjectExecutor.convertToSwift(project)
4748
}
4849
}
4950

src/main/kotlin/com/compiler/server/model/ExecutionResult.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,14 @@ class JunitExecutionResult(
8282
override var compilerDiagnostics: CompilerDiagnostics = CompilerDiagnostics()
8383
) : ExecutionResult(compilerDiagnostics, exception)
8484

85+
class SwiftExportResult(
86+
val swiftCode: String,
87+
override var exception: ExceptionDescriptor? = null,
88+
@field:JsonProperty("errors")
89+
override var compilerDiagnostics: CompilerDiagnostics = CompilerDiagnostics()
90+
) : ExecutionResult(compilerDiagnostics, exception)
91+
92+
8593
private fun unEscapeOutput(value: String) = value.replace("&amp;lt;".toRegex(), "<")
8694
.replace("&amp;gt;".toRegex(), ">")
8795
.replace("\r", "")

src/main/kotlin/com/compiler/server/model/KotlinTranslatableCompiler.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,6 @@ package com.compiler.server.model
22

33
enum class KotlinTranslatableCompiler {
44
JS,
5-
WASM
5+
WASM,
6+
SWIFT,
67
}

src/main/kotlin/com/compiler/server/model/Project.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@ enum class ProjectType(@JsonValue val id: String) {
1919
JS("js"),
2020
CANVAS("canvas"),
2121
JS_IR("js-ir"),
22-
WASM("wasm");
22+
WASM("wasm"),
23+
SWIFT_EXPORT("swift-export")
24+
;
2325

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

src/main/kotlin/com/compiler/server/service/KotlinProjectExecutor.kt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ class KotlinProjectExecutor(
1717
private val completionProvider: CompletionProvider,
1818
private val version: VersionInfo,
1919
private val kotlinToJSTranslator: KotlinToJSTranslator,
20+
private val swiftExportTranslator: SwiftExportTranslator,
2021
private val kotlinEnvironment: KotlinEnvironment,
2122
private val loggerDetailsStreamer: LoggerDetailsStreamer? = null,
2223
) {
@@ -52,6 +53,10 @@ class KotlinProjectExecutor(
5253
return convertWasmWithConverter(project, kotlinToJSTranslator::doTranslateWithWasm)
5354
}
5455

56+
fun convertToSwift(project: Project): SwiftExportResult {
57+
return convertSwiftWithConverter(project)
58+
}
59+
5560
fun complete(project: Project, line: Int, character: Int): List<Completion> {
5661
return kotlinEnvironment.environment {
5762
val file = getFilesFrom(project, it).first()
@@ -69,6 +74,7 @@ class KotlinProjectExecutor(
6974
ProjectType.JAVA, ProjectType.JUNIT -> compileToJvm(project).compilerDiagnostics
7075
ProjectType.CANVAS, ProjectType.JS, ProjectType.JS_IR -> convertToJsIr(project).compilerDiagnostics
7176
ProjectType.WASM -> convertToWasm(project).compilerDiagnostics
77+
ProjectType.SWIFT_EXPORT -> convertToSwift(project).compilerDiagnostics
7278
}
7379
} catch (e: Exception) {
7480
log.warn("Exception in getting highlight. Project: $project", e)
@@ -97,6 +103,15 @@ class KotlinProjectExecutor(
97103
}.also { logExecutionResult(project, it) }
98104
}
99105

106+
private fun convertSwiftWithConverter(
107+
project: Project,
108+
): SwiftExportResult {
109+
return kotlinEnvironment.environment { environment ->
110+
val files = getFilesFrom(project, environment).map { it.kotlinFile }
111+
swiftExportTranslator.translate(files)
112+
}.also { logExecutionResult(project, it) }
113+
}
114+
100115
private fun logExecutionResult(project: Project, executionResult: ExecutionResult) {
101116
loggerDetailsStreamer?.logExecutionResult(
102117
executionResult,
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
package com.compiler.server
2+
3+
import com.compiler.server.base.BaseExecutorTest
4+
import org.junit.jupiter.api.Test
5+
import kotlin.test.assertContains
6+
import kotlin.test.assertEquals
7+
import kotlin.test.assertTrue
8+
9+
class SwiftConverterTest : BaseExecutorTest() {
10+
11+
private fun exactTest(input: String, expected: String) {
12+
val actual = translateToSwift(input)
13+
assertEquals(expected, actual.swiftCode.trimEnd())
14+
}
15+
16+
private fun containsTest(input: String, expected: String) {
17+
val actual = translateToSwift(input)
18+
assertContains(actual.swiftCode.trimEnd(), expected)
19+
}
20+
21+
private fun shouldFailTest(input: String) {
22+
val actual = translateToSwift(input)
23+
assertTrue(actual.hasErrors())
24+
}
25+
26+
@Test
27+
fun basicSwiftExportTest() = containsTest(
28+
input = """
29+
fun main() {}
30+
""".trimIndent(),
31+
expected = "public func main() -> Swift.Void"
32+
)
33+
34+
@Test
35+
fun `use stdlib declaration`() = containsTest(
36+
input = "fun foo(): UInt = 42",
37+
expected = """
38+
public func foo() -> Swift.UInt32 {
39+
fatalError()
40+
}
41+
""".trimIndent()
42+
)
43+
44+
@Test
45+
fun `class declaration`() = exactTest(
46+
input = "public class MyClass { public fun A() {}}",
47+
expected = """
48+
import KotlinBridges
49+
import KotlinRuntime
50+
51+
public class MyClass {
52+
public init() {
53+
fatalError()
54+
}
55+
public func A() -> Swift.Void {
56+
fatalError()
57+
}
58+
}
59+
""".trimIndent()
60+
)
61+
62+
@Test
63+
fun `simple packages`() = exactTest(
64+
input = """
65+
package foo.bar
66+
67+
val myProperty: Int = 42
68+
""".trimIndent(),
69+
expected = """
70+
import KotlinBridges
71+
import KotlinRuntime
72+
73+
public extension Playground.foo.bar {
74+
public static var myProperty: Swift.Int32 {
75+
get {
76+
fatalError()
77+
}
78+
}
79+
}
80+
public enum foo {
81+
public enum bar {
82+
}
83+
}
84+
""".trimIndent()
85+
)
86+
87+
@Test
88+
fun `invalid code`() = exactTest(
89+
input = "abracadabra",
90+
// For now, we unconditionally import a few modules. Will be gone in the future versions
91+
expected = """
92+
import KotlinBridges
93+
import KotlinRuntime
94+
""".trimIndent()
95+
)
96+
97+
@Test
98+
fun `more invalid code`() = shouldFailTest(
99+
input = "fun foo(): Bar = error()",
100+
)
101+
}

src/test/kotlin/com/compiler/server/base/BaseExecutorTest.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ class BaseExecutorTest {
5656

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

59+
fun translateToSwift(code: String) = testRunner.translateToSwift(code)
60+
5961
fun runWithException(code: String, contains: String, message: String? = null) = testRunner.runWithException(code, contains, message)
6062

6163
fun version() = testRunner.getVersion()

src/test/kotlin/com/compiler/server/generator/TestProjectRunner.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,11 @@ class TestProjectRunner {
6161
return kotlinProjectExecutor.convertToJsIr(project)
6262
}
6363

64+
fun translateToSwift(code: String): SwiftExportResult {
65+
val project = generateSingleProject(text = code, projectType = ProjectType.SWIFT_EXPORT)
66+
return kotlinProjectExecutor.convertToSwift(project)
67+
}
68+
6469
fun runWithException(code: String, contains: String, message: String? = null): ExecutionResult {
6570
val project = generateSingleProject(text = code)
6671
val result = kotlinProjectExecutor.run(project)

swift-export-playground/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
An implementation of Swift export for Kotlin Playground.
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
plugins {
2+
kotlin("jvm")
3+
}
4+
5+
repositories {
6+
mavenCentral()
7+
// For Analysis API components
8+
maven("https://maven.pkg.jetbrains.space/kotlin/p/kotlin/kotlin-ide-plugin-dependencies")
9+
maven("https://maven.pkg.jetbrains.space/kotlin/p/kotlin/swift-export-experimental")
10+
}
11+
12+
val kotlinVersion = rootProject.properties["systemProp.kotlinVersion"]
13+
val swiftExportVersion = rootProject.properties["systemProp.swiftExportVersion"]
14+
15+
dependencies {
16+
implementation("org.jetbrains.kotlin:kotlin-compiler:$kotlinVersion")
17+
// For K/N Distribution class
18+
implementation("org.jetbrains.kotlin:kotlin-native-utils:$kotlinVersion")
19+
20+
// Analysis API components which are required for the Swift export
21+
implementation("org.jetbrains.kotlin:analysis-api-standalone-for-ide:$kotlinVersion") { isTransitive = false }
22+
implementation("org.jetbrains.kotlin:high-level-api-for-ide:$kotlinVersion") { isTransitive = false }
23+
implementation("org.jetbrains.kotlin:high-level-api-fir-for-ide:$kotlinVersion") { isTransitive = false }
24+
implementation("org.jetbrains.kotlin:high-level-api-impl-base-for-ide:$kotlinVersion") { isTransitive = false }
25+
implementation("org.jetbrains.kotlin:low-level-api-fir-for-ide:$kotlinVersion") { isTransitive = false }
26+
implementation("org.jetbrains.kotlin:symbol-light-classes-for-ide:$kotlinVersion") { isTransitive = false }
27+
28+
// Swift export not-yet-published dependencies.
29+
implementation("org.jetbrains.kotlin:sir:$swiftExportVersion") { isTransitive = false }
30+
implementation("org.jetbrains.kotlin:sir-providers:$swiftExportVersion") { isTransitive = false }
31+
implementation("org.jetbrains.kotlin:sir-light-classes:$swiftExportVersion") { isTransitive = false }
32+
implementation("org.jetbrains.kotlin:sir-printer:$swiftExportVersion") { isTransitive = false }
33+
34+
testImplementation("junit:junit:4.13.2")
35+
testImplementation("org.jetbrains.kotlin:kotlin-test:$kotlinVersion")
36+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import org.jetbrains.kotlin.analysis.api.KtAnalysisSession
2+
import org.jetbrains.kotlin.sir.providers.SirSession
3+
import org.jetbrains.kotlin.sir.providers.impl.*
4+
import org.jetbrains.sir.lightclasses.SirDeclarationFromKtSymbolProvider
5+
6+
internal class PlaygroundSirSession(
7+
private val ktAnalysisSession: KtAnalysisSession,
8+
) : SirSession {
9+
override val declarationNamer = SirDeclarationNamerImpl()
10+
override val enumGenerator = SirEnumGeneratorImpl()
11+
override val moduleProvider = SirSingleModuleProvider("Playground", "KotlinBridges")
12+
override val declarationProvider = CachingSirDeclarationProvider(
13+
declarationsProvider = SirDeclarationFromKtSymbolProvider(
14+
ktAnalysisSession = ktAnalysisSession,
15+
sirSession = sirSession,
16+
)
17+
)
18+
override val parentProvider = SirParentProviderImpl(
19+
ktAnalysisSession = ktAnalysisSession,
20+
sirSession = sirSession,
21+
)
22+
override val typeProvider = SirTypeProviderImpl(
23+
ktAnalysisSession = ktAnalysisSession,
24+
sirSession = sirSession,
25+
)
26+
override val visibilityChecker = SirVisibilityCheckerImpl(
27+
ktAnalysisSession = ktAnalysisSession,
28+
sirSession = sirSession,
29+
)
30+
override val childrenProvider = SirDeclarationChildrenProviderImpl(
31+
ktAnalysisSession = ktAnalysisSession,
32+
sirSession = sirSession,
33+
)
34+
}

0 commit comments

Comments
 (0)