[Swift export] Add swift-export-playground module
To mitigate problems with artifacts versioning at Kotlin Playground,
we package all required dependencies under a single module.
^KT-68877
diff --git a/generators/sir-tests-generator/build.gradle.kts b/generators/sir-tests-generator/build.gradle.kts
index a074a7e..2cd6d4d 100644
--- a/generators/sir-tests-generator/build.gradle.kts
+++ b/generators/sir-tests-generator/build.gradle.kts
@@ -10,6 +10,7 @@
dependencies {
implementation(projectTests(":native:swift:sir-compiler-bridge"))
implementation(projectTests(":native:swift:swift-export-standalone"))
+ implementation(projectTests(":native:swift:swift-export-playground"))
implementation(projectTests(":generators:test-generator"))
runtimeOnly(projectTests(":analysis:analysis-test-framework"))
runtimeOnly(libs.junit.jupiter.api)
diff --git a/generators/sir-tests-generator/main/org/jetbrains/kotlin/generators/tests/native/swift/sir/GenerateSirTests.kt b/generators/sir-tests-generator/main/org/jetbrains/kotlin/generators/tests/native/swift/sir/GenerateSirTests.kt
index 827a850..6927443 100644
--- a/generators/sir-tests-generator/main/org/jetbrains/kotlin/generators/tests/native/swift/sir/GenerateSirTests.kt
+++ b/generators/sir-tests-generator/main/org/jetbrains/kotlin/generators/tests/native/swift/sir/GenerateSirTests.kt
@@ -11,7 +11,7 @@
import org.jetbrains.kotlin.konan.test.blackbox.support.group.UseStandardTestCaseGroupProvider
import org.jetbrains.kotlin.sir.bridge.AbstractKotlinSirBridgeTest
import org.jetbrains.kotlin.swiftexport.standalone.AbstractKlibBasedSwiftRunnerTest
-
+import org.jetbrains.kotlin.swiftexport.playground.AbstractPlaygroundTranslatorTest
fun main() {
System.setProperty("java.awt.headless", "true")
@@ -40,5 +40,15 @@
model("", extension = null, recursive = false)
}
}
+ testGroup(
+ "native/swift/swift-export-playground/tests-gen/",
+ "native/swift/swift-export-playground/testData"
+ ) {
+ testClass<AbstractPlaygroundTranslatorTest>(
+ suiteTestClassName = "SwiftExportPlaygroundTestGenerated",
+ ) {
+ model("")
+ }
+ }
}
}
diff --git a/native/swift/swift-export-playground/README.md b/native/swift/swift-export-playground/README.md
new file mode 100644
index 0000000..d40cd59
--- /dev/null
+++ b/native/swift/swift-export-playground/README.md
@@ -0,0 +1,5 @@
+# Swift Export for Kotlin Playground
+
+Swift export is accessible at https://play.kotlinlang.org to provide an easy way for folks to check out the generated API.
+This modules packages all required dependencies into a single artifact with a pretty dumb API surface:
+take Kotlin file, return Swift API for it.
\ No newline at end of file
diff --git a/native/swift/swift-export-playground/build.gradle.kts b/native/swift/swift-export-playground/build.gradle.kts
new file mode 100644
index 0000000..f92708d
--- /dev/null
+++ b/native/swift/swift-export-playground/build.gradle.kts
@@ -0,0 +1,62 @@
+
+plugins {
+ kotlin("jvm")
+ id("jps-compatible")
+}
+
+description = "High-level Swift export API for Kotlin Playground"
+
+kotlin {
+ explicitApi()
+}
+
+dependencies {
+ compileOnly(kotlinStdlib())
+
+ implementation(project(":native:swift:sir"))
+ implementation(project(":native:swift:sir-light-classes"))
+ implementation(project(":native:swift:sir-printer"))
+ implementation(project(":native:swift:sir-providers"))
+
+ implementation(project(":analysis:analysis-api"))
+ implementation(project(":analysis:analysis-api-standalone"))
+
+ testApi(platform(libs.junit.bom))
+ testRuntimeOnly(libs.junit.jupiter.engine)
+ testImplementation(libs.junit.jupiter.api)
+
+ testRuntimeOnly(projectTests(":analysis:low-level-api-fir"))
+ testRuntimeOnly(projectTests(":analysis:analysis-api-impl-base"))
+ testImplementation(projectTests(":analysis:analysis-api-fir"))
+ testImplementation(projectTests(":analysis:analysis-test-framework"))
+ testImplementation(projectTests(":compiler:tests-common"))
+ testImplementation(projectTests(":compiler:tests-common-new"))
+
+ if (!kotlinBuildProperties.isInJpsBuildIdeaSync) {
+ testApi(projectTests(":native:native.tests"))
+ }
+}
+
+sourceSets {
+ "main" { projectDefault() }
+ "test" {
+ projectDefault()
+ generatedTestDir()
+ }
+}
+
+val testDataDir = projectDir.resolve("testData")
+
+val test by nativeTest("test", null) {
+ inputs.dir(testDataDir)
+ workingDir = rootDir
+ useJUnitPlatform { }
+}
+
+testsJar()
+
+if (kotlinBuildProperties.isSwiftExportPluginPublishingEnabled) {
+ publish()
+}
+
+runtimeJar(rewriteDefaultJarDepsToShadedCompiler())
\ No newline at end of file
diff --git a/native/swift/swift-export-playground/src/org/jetbrains/kotlin/swiftexport/playground/PlaygroundSirSession.kt b/native/swift/swift-export-playground/src/org/jetbrains/kotlin/swiftexport/playground/PlaygroundSirSession.kt
new file mode 100644
index 0000000..b91b6ec
--- /dev/null
+++ b/native/swift/swift-export-playground/src/org/jetbrains/kotlin/swiftexport/playground/PlaygroundSirSession.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2010-2024 JetBrains s.r.o. and Kotlin Programming Language contributors.
+ * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
+ */
+
+package org.jetbrains.kotlin.swiftexport.playground
+
+import org.jetbrains.kotlin.analysis.api.projectStructure.KaModule
+import org.jetbrains.kotlin.name.FqName
+import org.jetbrains.kotlin.sir.SirModule
+import org.jetbrains.kotlin.sir.providers.SirSession
+import org.jetbrains.kotlin.sir.providers.SirTrampolineDeclarationsProvider
+import org.jetbrains.kotlin.sir.providers.SirTypeProvider
+import org.jetbrains.kotlin.sir.providers.impl.*
+import org.jetbrains.kotlin.sir.providers.utils.UnsupportedDeclarationReporter
+import org.jetbrains.sir.lightclasses.SirDeclarationFromKtSymbolProvider
+
+internal class PlaygroundSirSession(
+ ktModule: KaModule,
+ moduleForPackageEnums: SirModule,
+ unsupportedDeclarationReporter: UnsupportedDeclarationReporter,
+ targetPackageFqName: FqName?,
+) : SirSession {
+ override val declarationNamer = SirDeclarationNamerImpl()
+ override val moduleProvider = SirSingleModuleProvider("Playground")
+ override val declarationProvider = CachingSirDeclarationProvider(
+ declarationsProvider = SirDeclarationFromKtSymbolProvider(
+ ktModule = ktModule,
+ sirSession = sirSession,
+ )
+ )
+ private val enumGenerator = SirEnumGeneratorImpl(moduleForPackageEnums)
+ override val parentProvider = SirParentProviderImpl(
+ sirSession = sirSession,
+ packageEnumGenerator = enumGenerator,
+ )
+ override val typeProvider = SirTypeProviderImpl(
+ errorTypeStrategy = SirTypeProvider.ErrorTypeStrategy.ErrorType,
+ unsupportedTypeStrategy = SirTypeProvider.ErrorTypeStrategy.ErrorType,
+ sirSession = sirSession,
+ )
+ override val visibilityChecker = SirVisibilityCheckerImpl(unsupportedDeclarationReporter)
+ override val childrenProvider = SirDeclarationChildrenProviderImpl(
+ sirSession = sirSession,
+ )
+
+ override val trampolineDeclarationsProvider: SirTrampolineDeclarationsProvider =
+ SirTrampolineDeclarationsProviderImpl(sirSession, targetPackageFqName, enumGenerator)
+}
\ No newline at end of file
diff --git a/native/swift/swift-export-playground/src/org/jetbrains/kotlin/swiftexport/playground/PlaygroundTranslator.kt b/native/swift/swift-export-playground/src/org/jetbrains/kotlin/swiftexport/playground/PlaygroundTranslator.kt
new file mode 100644
index 0000000..1d71829
--- /dev/null
+++ b/native/swift/swift-export-playground/src/org/jetbrains/kotlin/swiftexport/playground/PlaygroundTranslator.kt
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2010-2024 JetBrains s.r.o. and Kotlin Programming Language contributors.
+ * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
+ */
+
+package org.jetbrains.kotlin.swiftexport.playground
+
+import org.jetbrains.kotlin.analysis.api.analyze
+import org.jetbrains.kotlin.analysis.api.projectStructure.KaModule
+import org.jetbrains.kotlin.analysis.api.standalone.buildStandaloneAnalysisAPISession
+import org.jetbrains.kotlin.analysis.project.structure.builder.buildKtLibraryModule
+import org.jetbrains.kotlin.analysis.project.structure.builder.buildKtSourceModule
+import org.jetbrains.kotlin.platform.konan.NativePlatforms
+import org.jetbrains.kotlin.psi.KtFile
+import org.jetbrains.kotlin.sir.SirFunctionBody
+import org.jetbrains.kotlin.sir.SirModule
+import org.jetbrains.kotlin.sir.SirMutableDeclarationContainer
+import org.jetbrains.kotlin.sir.builder.buildModule
+import org.jetbrains.kotlin.sir.providers.utils.SimpleUnsupportedDeclarationReporter
+import org.jetbrains.kotlin.sir.util.addChild
+import org.jetbrains.sir.printer.SirAsSwiftSourcesPrinter
+import java.nio.file.Path
+
+/**
+ * [stdlibPath] is a path to stdlib.klib which is required to properly resolve references to declarations from the standard library.
+ */
+public class PlaygroundTranslator(
+ private val stdlibPath: Path?
+) {
+
+ /**
+ * Translate public API of the given [sourceFile] to Swift sources.
+ * [stdlibPath] is a path to stdlib.klib which is required to properly resolve references from [sourceFile].
+ */
+ public fun translate(sourceFile: Path): String {
+ val (ktModule, sources) = collectModuleAndSources(sourceFile, stdlibPath)
+
+ return analyze(ktModule) {
+ val pkgModule = buildModule {
+ name = "pkg"
+ }
+ val unsupportedDeclarationReporter = SimpleUnsupportedDeclarationReporter()
+ val sirSession = PlaygroundSirSession(ktModule, pkgModule, unsupportedDeclarationReporter, targetPackageFqName = null)
+ val sirModule: SirModule = with(sirSession) {
+ ktModule.sirModule().also {
+ sources.flatMap { file ->
+ file.symbol.fileScope.extractDeclarations(useSiteSession)
+ }.forEach { topLevelDeclaration ->
+ val parent = topLevelDeclaration.parent as? SirMutableDeclarationContainer
+ ?: error("top level declaration can contain only module or extension to package as a parent")
+ parent.addChild { topLevelDeclaration }
+ }
+ }
+ }
+ SirAsSwiftSourcesPrinter.print(
+ sirModule,
+ stableDeclarationsOrder = true,
+ renderDocComments = true,
+ emptyBodyStub = SirFunctionBody(
+ listOf("stub()")
+ )
+ )
+ }
+ }
+
+ private fun collectModuleAndSources(
+ sourceRoot: Path,
+ stdlibPath: Path?,
+ ): Pair<KaModule, List<KtFile>> {
+ val analysisAPISession = buildStandaloneAnalysisAPISession {
+ buildKtModuleProvider {
+ platform = NativePlatforms.unspecifiedNativePlatform
+
+ val stdlib = stdlibPath?.let {
+ addModule(
+ buildKtLibraryModule {
+ addBinaryRoot(it)
+ platform = NativePlatforms.unspecifiedNativePlatform
+ libraryName = "stdlib"
+ }
+ )
+ }
+
+ addModule(
+ buildKtSourceModule {
+ addSourceRoot(sourceRoot)
+ platform = NativePlatforms.unspecifiedNativePlatform
+ moduleName = "Playground"
+ if (stdlib != null) {
+ addRegularDependency(stdlib)
+ }
+ }
+ )
+ }
+ }
+
+ val (sourceModule, rawFiles) = analysisAPISession.modulesWithFiles.entries.single()
+ return sourceModule to rawFiles.filterIsInstance<KtFile>()
+ }
+}
\ No newline at end of file
diff --git a/native/swift/swift-export-playground/test/org/jetbrains/kotlin/swiftexport/playground/AbstractPlaygroundTranslatorTest.kt b/native/swift/swift-export-playground/test/org/jetbrains/kotlin/swiftexport/playground/AbstractPlaygroundTranslatorTest.kt
new file mode 100644
index 0000000..d1cd7df
--- /dev/null
+++ b/native/swift/swift-export-playground/test/org/jetbrains/kotlin/swiftexport/playground/AbstractPlaygroundTranslatorTest.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2010-2024 JetBrains s.r.o. and Kotlin Programming Language contributors.
+ * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
+ */
+
+package org.jetbrains.kotlin.swiftexport.playground
+
+import com.intellij.testFramework.TestDataFile
+import org.jetbrains.kotlin.konan.target.Distribution
+import org.jetbrains.kotlin.test.KotlinTestUtils
+import kotlin.io.path.Path
+import kotlin.io.path.nameWithoutExtension
+
+abstract class AbstractPlaygroundTranslatorTest {
+ protected fun runTest(@TestDataFile testPath: String) {
+ val sourceFile = Path(testPath)
+ val actual = PlaygroundTranslator(Path(KotlinNativeDistribution.stdlibPath)).translate(sourceFile)
+ val expectedFile = sourceFile.resolveSibling("${sourceFile.nameWithoutExtension}.swift")
+ KotlinTestUtils.assertEqualsToFile(expectedFile, actual)
+ }
+}
+
+private object KotlinNativeDistribution {
+ val stdlibPath: String
+ get() = Distribution(System.getProperty("kotlin.internal.native.test.nativeHome")).stdlib
+}
diff --git a/native/swift/swift-export-playground/testData/invalid.kt b/native/swift/swift-export-playground/testData/invalid.kt
new file mode 100644
index 0000000..9660ff0
--- /dev/null
+++ b/native/swift/swift-export-playground/testData/invalid.kt
@@ -0,0 +1,5 @@
+vax abracadabra
+
+val x: UndeclaredType = TODO()
+
+val y: () -> Unit = TODO()
diff --git a/native/swift/swift-export-playground/testData/invalid.swift b/native/swift/swift-export-playground/testData/invalid.swift
new file mode 100644
index 0000000..b3b2d16
--- /dev/null
+++ b/native/swift/swift-export-playground/testData/invalid.swift
@@ -0,0 +1,10 @@
+public var x: ERROR_TYPE {
+ get {
+ stub()
+ }
+}
+public var y: Swift.Never {
+ get {
+ stub()
+ }
+}
\ No newline at end of file
diff --git a/native/swift/swift-export-playground/testData/packaged.kt b/native/swift/swift-export-playground/testData/packaged.kt
new file mode 100644
index 0000000..c68bfee
--- /dev/null
+++ b/native/swift/swift-export-playground/testData/packaged.kt
@@ -0,0 +1,3 @@
+package org.me.playground
+
+class Foo
\ No newline at end of file
diff --git a/native/swift/swift-export-playground/testData/packaged.swift b/native/swift/swift-export-playground/testData/packaged.swift
new file mode 100644
index 0000000..d26cb63
--- /dev/null
+++ b/native/swift/swift-export-playground/testData/packaged.swift
@@ -0,0 +1,15 @@
+@_exported import pkg
+import KotlinRuntime
+
+public extension pkg.org.me.playground {
+ public class Foo : KotlinRuntime.KotlinBase {
+ public override init() {
+ stub()
+ }
+ public override init(
+ __externalRCRef: Swift.UInt
+ ) {
+ stub()
+ }
+ }
+}
\ No newline at end of file
diff --git a/native/swift/swift-export-playground/testData/smoke.kt b/native/swift/swift-export-playground/testData/smoke.kt
new file mode 100644
index 0000000..874783e
--- /dev/null
+++ b/native/swift/swift-export-playground/testData/smoke.kt
@@ -0,0 +1,22 @@
+fun foo() { println("hello") }
+
+class A(
+ val boolProp: Boolean,
+ val intProp: Int,
+ val floatProp: Float,
+ val refProp: B,
+)
+
+class B
+
+class C {
+ class D {
+ class E {
+
+ }
+ }
+
+ fun method(): D.E = D.E()
+}
+
+typealias CDE = C.D.E
\ No newline at end of file
diff --git a/native/swift/swift-export-playground/testData/smoke.swift b/native/swift/swift-export-playground/testData/smoke.swift
new file mode 100644
index 0000000..802061f
--- /dev/null
+++ b/native/swift/swift-export-playground/testData/smoke.swift
@@ -0,0 +1,84 @@
+import KotlinRuntime
+
+public typealias CDE = Playground.C.D.E
+public class A : KotlinRuntime.KotlinBase {
+ public var boolProp: Swift.Bool {
+ get {
+ stub()
+ }
+ }
+ public var floatProp: Swift.Float {
+ get {
+ stub()
+ }
+ }
+ public var intProp: Swift.Int32 {
+ get {
+ stub()
+ }
+ }
+ public var refProp: Playground.B {
+ get {
+ stub()
+ }
+ }
+ public override init(
+ __externalRCRef: Swift.UInt
+ ) {
+ stub()
+ }
+ public init(
+ boolProp: Swift.Bool,
+ intProp: Swift.Int32,
+ floatProp: Swift.Float,
+ refProp: Playground.B
+ ) {
+ stub()
+ }
+}
+public class B : KotlinRuntime.KotlinBase {
+ public override init() {
+ stub()
+ }
+ public override init(
+ __externalRCRef: Swift.UInt
+ ) {
+ stub()
+ }
+}
+public class C : KotlinRuntime.KotlinBase {
+ public class D : KotlinRuntime.KotlinBase {
+ public class E : KotlinRuntime.KotlinBase {
+ public override init() {
+ stub()
+ }
+ public override init(
+ __externalRCRef: Swift.UInt
+ ) {
+ stub()
+ }
+ }
+ public override init() {
+ stub()
+ }
+ public override init(
+ __externalRCRef: Swift.UInt
+ ) {
+ stub()
+ }
+ }
+ public override init() {
+ stub()
+ }
+ public override init(
+ __externalRCRef: Swift.UInt
+ ) {
+ stub()
+ }
+ public func method() -> Playground.C.D.E {
+ stub()
+ }
+}
+public func foo() -> Swift.Void {
+ stub()
+}
\ No newline at end of file
diff --git a/native/swift/swift-export-playground/testData/stdlib_usages.kt b/native/swift/swift-export-playground/testData/stdlib_usages.kt
new file mode 100644
index 0000000..2b75072
--- /dev/null
+++ b/native/swift/swift-export-playground/testData/stdlib_usages.kt
@@ -0,0 +1,4 @@
+val ui: UInt = 0
+
+// Is not supported properly yet.
+val s: String = "x"
\ No newline at end of file
diff --git a/native/swift/swift-export-playground/testData/stdlib_usages.swift b/native/swift/swift-export-playground/testData/stdlib_usages.swift
new file mode 100644
index 0000000..4e8e5e9b
--- /dev/null
+++ b/native/swift/swift-export-playground/testData/stdlib_usages.swift
@@ -0,0 +1,10 @@
+public var s: Swift.Never {
+ get {
+ stub()
+ }
+}
+public var ui: Swift.UInt32 {
+ get {
+ stub()
+ }
+}
\ No newline at end of file
diff --git a/native/swift/swift-export-playground/tests-gen/org/jetbrains/kotlin/swiftexport/playground/SwiftExportPlaygroundTestGenerated.java b/native/swift/swift-export-playground/tests-gen/org/jetbrains/kotlin/swiftexport/playground/SwiftExportPlaygroundTestGenerated.java
new file mode 100644
index 0000000..c5edfc0
--- /dev/null
+++ b/native/swift/swift-export-playground/tests-gen/org/jetbrains/kotlin/swiftexport/playground/SwiftExportPlaygroundTestGenerated.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2010-2024 JetBrains s.r.o. and Kotlin Programming Language contributors.
+ * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
+ */
+
+package org.jetbrains.kotlin.swiftexport.playground;
+
+import com.intellij.testFramework.TestDataPath;
+import org.jetbrains.kotlin.test.util.KtTestUtil;
+import org.jetbrains.kotlin.test.TestMetadata;
+import org.junit.jupiter.api.Test;
+
+import java.io.File;
+import java.util.regex.Pattern;
+
+/** This class is generated by {@link org.jetbrains.kotlin.generators.tests.native.swift.sir.GenerateSirTestsKt}. DO NOT MODIFY MANUALLY */
+@SuppressWarnings("all")
+@TestMetadata("native/swift/swift-export-playground/testData")
+@TestDataPath("$PROJECT_ROOT")
+public class SwiftExportPlaygroundTestGenerated extends AbstractPlaygroundTranslatorTest {
+ @Test
+ public void testAllFilesPresentInTestData() {
+ KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("native/swift/swift-export-playground/testData"), Pattern.compile("^(.+)\\.kt$"), null, true);
+ }
+
+ @Test
+ @TestMetadata("invalid.kt")
+ public void testInvalid() {
+ runTest("native/swift/swift-export-playground/testData/invalid.kt");
+ }
+
+ @Test
+ @TestMetadata("packaged.kt")
+ public void testPackaged() {
+ runTest("native/swift/swift-export-playground/testData/packaged.kt");
+ }
+
+ @Test
+ @TestMetadata("smoke.kt")
+ public void testSmoke() {
+ runTest("native/swift/swift-export-playground/testData/smoke.kt");
+ }
+
+ @Test
+ @TestMetadata("stdlib_usages.kt")
+ public void testStdlib_usages() {
+ runTest("native/swift/swift-export-playground/testData/stdlib_usages.kt");
+ }
+}
diff --git a/settings.gradle b/settings.gradle
index a8288bf..a372920 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -466,6 +466,7 @@
":native:swift:sir-compiler-bridge",
":native:swift:sir-providers",
":native:swift:swift-export-standalone",
+ ":native:swift:swift-export-playground",
":native:swift:swift-export-embeddable",
":generators:sir-tests-generator"