diff --git a/build.gradle.kts b/build.gradle.kts index f1528bb2..4f0a81fd 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -80,6 +80,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") diff --git a/common/src/main/kotlin/component/KotlinEnvironment.kt b/common/src/main/kotlin/component/KotlinEnvironment.kt index 56d50bbe..a7cfef19 100644 --- a/common/src/main/kotlin/component/KotlinEnvironment.kt +++ b/common/src/main/kotlin/component/KotlinEnvironment.kt @@ -21,6 +21,7 @@ import org.jetbrains.kotlin.serialization.js.JsModuleDescriptor import org.jetbrains.kotlin.serialization.js.KotlinJavascriptSerializationUtil import org.jetbrains.kotlin.serialization.js.ModuleKind import org.jetbrains.kotlin.utils.KotlinJavascriptMetadataUtils +import org.jetbrains.kotlin.wasm.config.WasmConfigurationKeys import java.io.File class KotlinEnvironment( @@ -89,15 +90,15 @@ class KotlinEnvironment( val wasmConfiguration: CompilerConfiguration = configuration.copy().apply { put(CommonConfigurationKeys.MODULE_NAME, "moduleId") put(JSConfigurationKeys.LIBRARIES, WASM_LIBRARIES) - put(JSConfigurationKeys.WASM_ENABLE_ARRAY_RANGE_CHECKS, false) - put(JSConfigurationKeys.WASM_ENABLE_ASSERTS, false) + put(WasmConfigurationKeys.WASM_ENABLE_ARRAY_RANGE_CHECKS, false) + put(WasmConfigurationKeys.WASM_ENABLE_ASSERTS, false) } val composeWasmConfiguration: CompilerConfiguration = configuration.copy().apply { put(CommonConfigurationKeys.MODULE_NAME, "moduleId") put(JSConfigurationKeys.LIBRARIES, COMPOSE_WASM_LIBRARIES) - put(JSConfigurationKeys.WASM_ENABLE_ARRAY_RANGE_CHECKS, false) - put(JSConfigurationKeys.WASM_ENABLE_ASSERTS, false) + put(WasmConfigurationKeys.WASM_ENABLE_ARRAY_RANGE_CHECKS, false) + put(WasmConfigurationKeys.WASM_ENABLE_ASSERTS, false) PluginCliParser.loadPluginsSafe( COMPOSE_WASM_COMPILER_PLUGINS, diff --git a/gradle.properties b/gradle.properties index 14d4c734..a4d843cb 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ -systemProp.kotlinVersion=2.0.0 +systemProp.kotlinVersion=2.1.0-dev-3023 systemProp.kotlinIdeVersion=1.9.20-506 systemProp.kotlinIdeVersionSuffix=IJ8109.175 systemProp.policy=executor.policy diff --git a/settings.gradle.kts b/settings.gradle.kts index aa2d316b..47b657bc 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -3,11 +3,13 @@ include(":executors") include(":indexation") include(":common") include(":dependencies") +include(":swift-export-playground") pluginManagement { repositories { gradlePluginPortal() mavenCentral() maven("https://maven.pkg.jetbrains.space/kotlin/p/kotlin/bootstrap") + maven("https://maven.pkg.jetbrains.space/kotlin/p/kotlin/dev") } } \ No newline at end of file diff --git a/src/main/kotlin/com/compiler/server/compiler/components/CliUtils.kt b/src/main/kotlin/com/compiler/server/compiler/components/CliUtils.kt index fdd633b1..3e9d296c 100644 --- a/src/main/kotlin/com/compiler/server/compiler/components/CliUtils.kt +++ b/src/main/kotlin/com/compiler/server/compiler/components/CliUtils.kt @@ -6,7 +6,6 @@ import com.compiler.server.model.ErrorDescriptor import com.compiler.server.model.ProjectSeveriry import com.compiler.server.model.TextInterval import org.jetbrains.kotlin.cli.common.CLICompiler -import org.jetbrains.kotlin.cli.common.CLITool import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity.* import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSourceLocation @@ -56,7 +55,7 @@ fun CLICompiler<*>.tryCompilation(inputDirectory: Path, inputFiles: List): 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()) + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/compiler/server/controllers/CompilerRestController.kt b/src/main/kotlin/com/compiler/server/controllers/CompilerRestController.kt index 92c219c5..df3f14dd 100644 --- a/src/main/kotlin/com/compiler/server/controllers/CompilerRestController.kt +++ b/src/main/kotlin/com/compiler/server/controllers/CompilerRestController.kt @@ -34,6 +34,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) {} + } } } diff --git a/src/main/kotlin/com/compiler/server/controllers/KotlinPlaygroundRestController.kt b/src/main/kotlin/com/compiler/server/controllers/KotlinPlaygroundRestController.kt index ffcc3d6f..11b8478e 100644 --- a/src/main/kotlin/com/compiler/server/controllers/KotlinPlaygroundRestController.kt +++ b/src/main/kotlin/com/compiler/server/controllers/KotlinPlaygroundRestController.kt @@ -51,6 +51,7 @@ class KotlinPlaygroundRestController(private val kotlinProjectExecutor: KotlinPr debugInfo = false, ) ProjectType.JUNIT -> kotlinProjectExecutor.test(project, addByteCode) + ProjectType.SWIFT_EXPORT -> kotlinProjectExecutor.convertToSwift(project) } } diff --git a/src/main/kotlin/com/compiler/server/model/ExecutionResult.kt b/src/main/kotlin/com/compiler/server/model/ExecutionResult.kt index fddd35b2..9bab597c 100644 --- a/src/main/kotlin/com/compiler/server/model/ExecutionResult.kt +++ b/src/main/kotlin/com/compiler/server/model/ExecutionResult.kt @@ -89,6 +89,14 @@ class JunitExecutionResult( jvmBytecode: String? = null, ) : JvmExecutionResult(compilerDiagnostics, exception, jvmBytecode) +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("&lt;".toRegex(), "<") .replace("&gt;".toRegex(), ">") .replace("\r", "") diff --git a/src/main/kotlin/com/compiler/server/model/KotlinTranslatableCompiler.kt b/src/main/kotlin/com/compiler/server/model/KotlinTranslatableCompiler.kt index 0f743056..bdeb2901 100644 --- a/src/main/kotlin/com/compiler/server/model/KotlinTranslatableCompiler.kt +++ b/src/main/kotlin/com/compiler/server/model/KotlinTranslatableCompiler.kt @@ -3,5 +3,6 @@ package com.compiler.server.model enum class KotlinTranslatableCompiler { JS, WASM, - COMPOSE_WASM + COMPOSE_WASM, + SWIFT_EXPORT, } \ No newline at end of file diff --git a/src/main/kotlin/com/compiler/server/model/Project.kt b/src/main/kotlin/com/compiler/server/model/Project.kt index b8afd6fe..dcb57cc4 100644 --- a/src/main/kotlin/com/compiler/server/model/Project.kt +++ b/src/main/kotlin/com/compiler/server/model/Project.kt @@ -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 diff --git a/src/main/kotlin/com/compiler/server/service/KotlinProjectExecutor.kt b/src/main/kotlin/com/compiler/server/service/KotlinProjectExecutor.kt index 923d63b8..80207495 100644 --- a/src/main/kotlin/com/compiler/server/service/KotlinProjectExecutor.kt +++ b/src/main/kotlin/com/compiler/server/service/KotlinProjectExecutor.kt @@ -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, ) { @@ -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 { return kotlinEnvironment.environment { val file = getFilesFrom(project, it).first() @@ -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) @@ -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, diff --git a/src/test/kotlin/com/compiler/server/SwiftConverterTest.kt b/src/test/kotlin/com/compiler/server/SwiftConverterTest.kt new file mode 100644 index 00000000..74b5a7c6 --- /dev/null +++ b/src/test/kotlin/com/compiler/server/SwiftConverterTest.kt @@ -0,0 +1,110 @@ +package com.compiler.server + +import com.compiler.server.base.BaseExecutorTest +import org.junit.jupiter.api.Test +import kotlin.test.assertContains +import kotlin.test.assertEquals + +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) + } + + @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 override init( + __externalRCRef: Swift.UInt + ) { + stub() + } + public func A() -> Swift.Void { + stub() + } + } + """.trimIndent() + ) + + @Test + fun `simple packages`() = exactTest( + input = """ + package foo.bar + + val myProperty: Int = 42 + """.trimIndent(), + expected = """ + @_exported import pkg + + public extension pkg.foo.bar { + public static var myProperty: Swift.Int32 { + get { + stub() + } + } + } + """.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() + ) + + @Test + fun `unsupported type declaration`() = exactTest( + input = """ + interface Foo + + fun produceFoo(): Foo = TODO() + """.trimIndent(), + expected = """ + public func produceFoo() -> Swift.Never { + stub() + } + """.trimIndent() + ) +} \ No newline at end of file diff --git a/src/test/kotlin/com/compiler/server/base/BaseExecutorTest.kt b/src/test/kotlin/com/compiler/server/base/BaseExecutorTest.kt index bc689bb1..16681c8f 100644 --- a/src/test/kotlin/com/compiler/server/base/BaseExecutorTest.kt +++ b/src/test/kotlin/com/compiler/server/base/BaseExecutorTest.kt @@ -65,6 +65,8 @@ class BaseExecutorTest { fun translateToJsIr(@Language("kotlin") code: String) = testRunner.translateToJsIr(code) + fun translateToSwift(code: String) = testRunner.translateToSwift(code) + fun runWithException(@Language("kotlin") code: String, contains: String, message: String? = null, addByteCode: Boolean = false) = testRunner.runWithException(code, contains, message, addByteCode) diff --git a/src/test/kotlin/com/compiler/server/generator/TestProjectRunner.kt b/src/test/kotlin/com/compiler/server/generator/TestProjectRunner.kt index 80528047..a75fb75b 100644 --- a/src/test/kotlin/com/compiler/server/generator/TestProjectRunner.kt +++ b/src/test/kotlin/com/compiler/server/generator/TestProjectRunner.kt @@ -75,6 +75,11 @@ class TestProjectRunner { ) } + fun translateToSwift(code: String): SwiftExportResult { + val project = generateSingleProject(text = code, projectType = ProjectType.SWIFT_EXPORT) + return kotlinProjectExecutor.convertToSwift(project) + } + fun runWithException( @Language("kotlin") code: String, diff --git a/swift-export-playground/README.md b/swift-export-playground/README.md new file mode 100644 index 00000000..c26b5951 --- /dev/null +++ b/swift-export-playground/README.md @@ -0,0 +1 @@ +An implementation of Swift export for Kotlin Playground. \ No newline at end of file diff --git a/swift-export-playground/build.gradle.kts b/swift-export-playground/build.gradle.kts new file mode 100644 index 00000000..68dc171b --- /dev/null +++ b/swift-export-playground/build.gradle.kts @@ -0,0 +1,35 @@ +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/dev") +} + +val kotlinVersion = rootProject.properties["systemProp.kotlinVersion"] +val aaVersion = "2.1.0-dev-2515" + +dependencies { + implementation("org.jetbrains.kotlin:kotlin-compiler:$aaVersion") + + // Analysis API components which are required for the Swift export + implementation("org.jetbrains.kotlin:analysis-api-standalone-for-ide:$aaVersion") { isTransitive = false } + implementation("org.jetbrains.kotlin:high-level-api-for-ide:$aaVersion") { isTransitive = false } + implementation("org.jetbrains.kotlin:high-level-api-fir-for-ide:$aaVersion") { isTransitive = false } + implementation("org.jetbrains.kotlin:high-level-api-impl-base-for-ide:$aaVersion") { isTransitive = false } + implementation("org.jetbrains.kotlin:low-level-api-fir-for-ide:$aaVersion") { isTransitive = false } + implementation("org.jetbrains.kotlin:symbol-light-classes-for-ide:$aaVersion") { isTransitive = false } + implementation("org.jetbrains.kotlin:analysis-api-platform-interface-for-ide:$aaVersion") { isTransitive = false } + + // Swift export not-yet-published dependencies. + implementation("org.jetbrains.kotlin:sir:$kotlinVersion") { isTransitive = false } + implementation("org.jetbrains.kotlin:sir-providers:$kotlinVersion") { isTransitive = false } + implementation("org.jetbrains.kotlin:sir-light-classes:$kotlinVersion") { isTransitive = false } + implementation("org.jetbrains.kotlin:sir-printer:$kotlinVersion") { isTransitive = false } + + testImplementation("junit:junit:4.13.2") + testImplementation("org.jetbrains.kotlin:kotlin-test:$kotlinVersion") +} \ No newline at end of file diff --git a/swift-export-playground/src/main/kotlin/PlaygroundSirSession.kt b/swift-export-playground/src/main/kotlin/PlaygroundSirSession.kt new file mode 100644 index 00000000..d647dbfe --- /dev/null +++ b/swift-export-playground/src/main/kotlin/PlaygroundSirSession.kt @@ -0,0 +1,42 @@ +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, + ) + ) + override 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) +} \ No newline at end of file diff --git a/swift-export-playground/src/main/kotlin/Runner.kt b/swift-export-playground/src/main/kotlin/Runner.kt new file mode 100644 index 00000000..353d746f --- /dev/null +++ b/swift-export-playground/src/main/kotlin/Runner.kt @@ -0,0 +1,89 @@ +import org.jetbrains.kotlin.analysis.api.analyze +import org.jetbrains.kotlin.analysis.api.standalone.buildStandaloneAnalysisAPISession +import org.jetbrains.kotlin.analysis.project.structure.KtModule +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 + +/** + * Translate public API of the given [sourceFile] to Swift. + * [stdlibPath] is a path to stdlib.klib which is required to properly resolve references from [sourceFile]. + */ +fun runSwiftExport( + sourceFile: Path, + stdlibPath: Path? +): String { + val (ktModule, sources) = collectModuleAndSources(sourceFile, "Playground", 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, + kotlinModuleName: String, + stdlibPath: Path?, +): Pair> { + 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 = kotlinModuleName + if (stdlib != null) { + addRegularDependency(stdlib) + } + } + ) + } + } + + val (sourceModule, rawFiles) = analysisAPISession.modulesWithFiles.entries.single() + return sourceModule to rawFiles.filterIsInstance() +} \ No newline at end of file diff --git a/swift-export-playground/src/test/kotlin/Tests.kt b/swift-export-playground/src/test/kotlin/Tests.kt new file mode 100644 index 00000000..cd5cc4bb --- /dev/null +++ b/swift-export-playground/src/test/kotlin/Tests.kt @@ -0,0 +1,110 @@ +import kotlin.io.path.* +import kotlin.test.Test +import kotlin.test.assertEquals + +class SwiftExportTests { + + private fun testSources(input: String, expect: String) { + val tempDir = createTempDirectory() + + val inputFormatted = input.trimIndent().trimEnd() + + val inputFile = (tempDir / "input.kt").also { it.writeText(inputFormatted) } + + val actual = runSwiftExport( + sourceFile = inputFile, + stdlibPath = null, + ) + val expectFormatted = expect.trimIndent().trimEnd() + + assertEquals(expectFormatted, actual) + } + + @Test + fun smoke() = testSources( + """ + fun foo(): Int = 5 + """, + """ + public func foo() -> Swift.Int32 { + stub() + } + """ + ) + + @Test + fun `class declaration`() = testSources( + """ + class A + """.trimIndent(), + """ + import KotlinRuntime + + public class A : KotlinRuntime.KotlinBase { + public override init() { + stub() + } + public override init( + __externalRCRef: Swift.UInt + ) { + stub() + } + } + """.trimIndent() + ) + + @Test + fun `object declaration`() = testSources( + """ + object O + """.trimIndent(), + """ + import KotlinRuntime + + public class O : KotlinRuntime.KotlinBase { + public static var shared: Playground.O { + get { + stub() + } + } + private override init() { + stub() + } + public override init( + __externalRCRef: Swift.UInt + ) { + stub() + } + } + """.trimIndent() + ) + + @Test + fun `typealias to basic type declaration`() = testSources( + """ + typealias MyInt = Int + """.trimIndent(), + """ + public typealias MyInt = Swift.Int32 + """.trimIndent() + ) + + @Test + fun `strings and chars`() = testSources( + """ + fun produceString(): String = "hello" + + fun firstChar(str: String): Char = str.first() + """.trimIndent(), + """ + public func firstChar( + str: Swift.String + ) -> Swift.Unicode.UTF16.CodeUnit { + stub() + } + public func produceString() -> Swift.String { + stub() + } + """.trimIndent() + ) +} \ No newline at end of file