Skip to content

Commit 9a58408

Browse files
committed
Basic Swift export
Minor hack: use wasm stdlib to avoid downloading the whole K/N distribution
1 parent 50e6205 commit 9a58408

File tree

18 files changed

+405
-2
lines changed

18 files changed

+405
-2
lines changed

build.gradle.kts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ allprojects {
4040
maven("https://maven.pkg.jetbrains.space/kotlin/p/kotlin/bootstrap")
4141
maven("https://maven.pkg.jetbrains.space/kotlin/p/wasm/experimental")
4242
maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
43+
maven("https://maven.pkg.jetbrains.space/kotlin/p/kotlin/swift-export-experimental")
4344
}
4445
afterEvaluate {
4546
dependencies {
@@ -74,6 +75,7 @@ dependencies {
7475
implementation("org.jetbrains.kotlin:core:231-$kotlinIdeVersion-$kotlinIdeVersionSuffix")
7576
implementation(project(":executors", configuration = "default"))
7677
implementation(project(":common", configuration = "default"))
78+
implementation(project(":swift-export-playground", configuration = "default"))
7779

7880
testImplementation("org.springframework.boot:spring-boot-starter-test") {
7981
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-RC2
22
systemProp.kotlinIdeVersion=1.9.20-506
33
systemProp.kotlinIdeVersionSuffix=IJ8109.175
4+
systemProp.swiftExportVersion=2.0.20-dev-3080
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
@@ -3,6 +3,7 @@ include(":executors")
33
include(":indexation")
44
include(":common")
55
include(":dependencies")
6+
include(":swift-export-playground")
67

78
pluginManagement {
89
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
@@ -28,6 +28,10 @@ class CompilerRestController(private val kotlinProjectExecutor: KotlinProjectExe
2828
KotlinTranslatableCompiler.JS -> kotlinProjectExecutor.convertToJsIr(project)
2929
KotlinTranslatableCompiler.WASM -> kotlinProjectExecutor.convertToWasm(project, debugInfo)
3030
KotlinTranslatableCompiler.COMPOSE_WASM -> kotlinProjectExecutor.convertToWasm(project, debugInfo)
31+
KotlinTranslatableCompiler.SWIFT -> kotlinProjectExecutor.convertToSwift(project).let {
32+
// TODO: A hack to avoid changing the return type of the function.
33+
object : TranslationResultWithJsCode(it.swiftCode, it.compilerDiagnostics, it.exception) {}
34+
}
3135
}
3236
}
3337

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ class KotlinPlaygroundRestController(private val kotlinProjectExecutor: KotlinPr
5050
debugInfo = false,
5151
)
5252
ProjectType.JUNIT -> kotlinProjectExecutor.test(project)
53+
ProjectType.SWIFT_EXPORT -> kotlinProjectExecutor.convertToSwift(project)
5354
}
5455
}
5556

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
@@ -3,5 +3,6 @@ package com.compiler.server.model
33
enum class KotlinTranslatableCompiler {
44
JS,
55
WASM,
6-
COMPOSE_WASM
6+
COMPOSE_WASM,
7+
SWIFT,
78
}

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

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

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

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, debugInfo, 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()
@@ -76,6 +81,7 @@ class KotlinProjectExecutor(
7681
project,
7782
debugInfo = false,
7883
).compilerDiagnostics
84+
ProjectType.SWIFT_EXPORT -> convertToSwift(project).compilerDiagnostics
7985
}
8086
} catch (e: Exception) {
8187
log.warn("Exception in getting highlight. Project: $project", e)
@@ -114,6 +120,15 @@ class KotlinProjectExecutor(
114120
}.also { logExecutionResult(project, it) }
115121
}
116122

123+
private fun convertSwiftWithConverter(
124+
project: Project,
125+
): SwiftExportResult {
126+
return kotlinEnvironment.environment { environment ->
127+
val files = getFilesFrom(project, environment).map { it.kotlinFile }
128+
swiftExportTranslator.translate(files)
129+
}.also { logExecutionResult(project, it) }
130+
}
131+
117132
private fun logExecutionResult(project: Project, executionResult: ExecutionResult) {
118133
loggerDetailsStreamer?.logExecutionResult(
119134
executionResult,
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
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 KotlinRuntime
49+
50+
public class MyClass : KotlinRuntime.KotlinBase {
51+
public override init() {
52+
fatalError()
53+
}
54+
public func A() -> Swift.Void {
55+
fatalError()
56+
}
57+
}
58+
""".trimIndent()
59+
)
60+
61+
@Test
62+
fun `simple packages`() = exactTest(
63+
input = """
64+
package foo.bar
65+
66+
val myProperty: Int = 42
67+
""".trimIndent(),
68+
expected = """
69+
public extension Playground.foo.bar {
70+
public static var myProperty: Swift.Int32 {
71+
get {
72+
fatalError()
73+
}
74+
}
75+
}
76+
public enum foo {
77+
public enum bar {
78+
}
79+
}
80+
""".trimIndent()
81+
)
82+
83+
@Test
84+
fun `invalid code`() = exactTest(
85+
input = "abracadabra",
86+
expected = """
87+
""".trimIndent()
88+
)
89+
90+
@Test
91+
fun `more invalid code`() = exactTest(
92+
input = "fun foo(): Bar = error()",
93+
expected = """
94+
public func foo() -> ERROR_TYPE {
95+
fatalError()
96+
}
97+
""".trimIndent()
98+
)
99+
}

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

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

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

63+
fun translateToSwift(code: String) = testRunner.translateToSwift(code)
64+
6365
fun runWithException(code: String, contains: String, message: String? = null) = testRunner.runWithException(code, contains, message)
6466

6567
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
@@ -63,6 +63,11 @@ class TestProjectRunner {
6363
)
6464
}
6565

66+
fun translateToSwift(code: String): SwiftExportResult {
67+
val project = generateSingleProject(text = code, projectType = ProjectType.SWIFT_EXPORT)
68+
return kotlinProjectExecutor.convertToSwift(project)
69+
}
70+
6671
fun runWithException(code: String, contains: String, message: String? = null): ExecutionResult {
6772
val project = generateSingleProject(text = code)
6873
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: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import org.jetbrains.kotlin.analysis.project.structure.KtModule
2+
import org.jetbrains.kotlin.sir.providers.SirSession
3+
import org.jetbrains.kotlin.sir.providers.SirTypeProvider
4+
import org.jetbrains.kotlin.sir.providers.impl.*
5+
import org.jetbrains.sir.lightclasses.SirDeclarationFromKtSymbolProvider
6+
7+
internal class PlaygroundSirSession(
8+
ktModule: KtModule,
9+
) : SirSession {
10+
override val declarationNamer = SirDeclarationNamerImpl()
11+
override val enumGenerator = SirEnumGeneratorImpl()
12+
override val moduleProvider = SirSingleModuleProvider("Playground")
13+
override val declarationProvider = CachingSirDeclarationProvider(
14+
declarationsProvider = SirDeclarationFromKtSymbolProvider(
15+
ktModule = ktModule,
16+
sirSession = sirSession,
17+
)
18+
)
19+
override val parentProvider = SirParentProviderImpl(
20+
sirSession = sirSession,
21+
)
22+
override val typeProvider = SirTypeProviderImpl(
23+
errorTypeStrategy = SirTypeProvider.ErrorTypeStrategy.ErrorType,
24+
unsupportedTypeStrategy = SirTypeProvider.ErrorTypeStrategy.ErrorType,
25+
sirSession = sirSession,
26+
)
27+
override val visibilityChecker = SirVisibilityCheckerImpl()
28+
override val childrenProvider = SirDeclarationChildrenProviderImpl(
29+
sirSession = sirSession,
30+
)
31+
}

0 commit comments

Comments
 (0)