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 4 commits into
base: 2.0.0-beta2-prepare
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,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 @@ -80,6 +81,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-RC3
systemProp.kotlinIdeVersion=1.9.20-506
systemProp.kotlinIdeVersionSuffix=IJ8109.175
systemProp.swiftExportVersion=2.0.20-dev-3623
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_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 @@ -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_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
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 {
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 func A() -> Swift.Void {
stub()
}
}
""".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 {
stub()
}
}
}
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 {
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 @@ -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,
)
}