[Swift Export]: KT-63280: Package inflation

Merge-request: KT-MR-13331
Merged-by: Gleb Lukianets <Gleb.Lukianets@jetbrains.com>
This commit is contained in:
Gleb Lukianets
2023-12-12 16:05:22 +00:00
committed by Space Team
parent 0b1c4b836a
commit 64ebec7955
17 changed files with 644 additions and 116 deletions
@@ -0,0 +1,88 @@
/*
* Copyright 2010-2023 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.sir.passes
import org.jetbrains.kotlin.sir.*
import org.jetbrains.kotlin.sir.builder.buildEnum
import org.jetbrains.kotlin.sir.builder.buildModule
import org.jetbrains.kotlin.sir.visitors.SirTransformer
/**
* Pass that for every occurring declaration in package x.y.z generates a mirroring type scope and puts it there.
* Right now, enums without cases are used for namespace simulation.
*/
public class SirInflatePackagesPass : SirPass<SirModule, Unit, SirModule> {
private data class Namespace(
val elements: MutableList<SirDeclaration> = mutableListOf(),
val children: MutableMap<String, Namespace> = mutableMapOf(),
) {
fun <R> reduce(transform: (List<String>, List<SirDeclaration>, List<R>) -> R): R {
fun reduceFrom(
node: Namespace,
rootPath: List<String>,
transform: (List<String>, List<SirDeclaration>, List<R>) -> R,
): R = transform(
rootPath,
node.elements,
node.children.map { reduceFrom(it.value, rootPath + it.key, transform) }
)
return reduceFrom(this, listOf(""), transform)
}
fun getOrCreate(path: List<String>): Namespace {
if (path.isEmpty()) {
return this
}
val key = path.first()
val next = children.getOrPut(key) { Namespace() }
return next.getOrCreate(path.drop(1))
}
}
private class Context(val root: Namespace = Namespace())
private object Transformer : SirTransformer<Context>() {
override fun <E : SirElement> transformElement(element: E, data: Context): E = element
override fun transformModule(module: SirModule, data: Context): SirModule = buildModule {
name = module.name
for (declaration in module.declarations) {
if (declaration is SirForeignDeclaration) {
val origin = declaration.origin
if (origin is SirOrigin.Foreign) {
// FIXME: for now we assume everything before the last dot is a package name.
// This should change as we add type declarations into the mix
val path = origin.path.dropLast(1)
data.root.getOrCreate(path).elements.add(declaration)
continue
}
}
declarations += declaration
}
val additions = data.root.reduce { path, declarations, children ->
buildEnum {
origin = SirOrigin.Namespace(path.drop(1))
name = path.last()
this.declarations += children
this.declarations += declarations
}
}
declarations += additions.declarations
}.also(SirDeclarationContainer::fixParents)
}
public override fun run(element: SirModule, data: Unit): SirModule = element.transform(Transformer, Context())
}
private fun SirDeclarationContainer.fixParents() = declarations
.onEach { it.parent = this }
.filterIsInstance<SirDeclarationContainer>()
.forEach(SirDeclarationContainer::fixParents)
@@ -11,7 +11,7 @@ import org.jetbrains.kotlin.sir.SirElement
* Swift IR is supposed to be transformed by a series of passes.
* This is a base interface that all such passes should implement.
*/
public interface SirPass<out R, in T> {
public interface SirPass<in E : SirElement, in T, out R> {
/**
* Executes the pass over the given [SirElement].
@@ -20,5 +20,7 @@ public interface SirPass<out R, in T> {
* @param data Additional data that is required to run the pass.
* @return The result of the pass.
*/
public fun run(element: SirElement, data: T): R
}
public fun run(element: E, data: T): R
}
public fun <E : SirElement, R> SirPass<E, Unit, R>.run(element: E): R = this.run(element, Unit)
@@ -8,7 +8,6 @@ package org.jetbrains.sir.passes.translation
import org.jetbrains.kotlin.sir.*
import org.jetbrains.kotlin.sir.builder.buildFunction
import org.jetbrains.kotlin.sir.util.SirSwiftModule
import org.jetbrains.kotlin.sir.KotlinFunction
import org.jetbrains.kotlin.sir.constants.*
import org.jetbrains.kotlin.sir.visitors.SirTransformerVoid
import org.jetbrains.sir.passes.SirPass
@@ -22,7 +21,7 @@ import java.lang.IllegalStateException
* or `element` does not contain origin of type `SirOrigin.KotlinEntity.Function`,
* returns original element.
*/
public class ForeignIntoSwiftFunctionTranslationPass : SirPass<SirElement, Nothing?> {
public class ForeignIntoSwiftFunctionTranslationPass : SirPass<SirElement, Nothing?, SirElement> {
private class Transformer : SirTransformerVoid() {
override fun <E : SirElement> transformElement(element: E): E {
@@ -31,7 +30,7 @@ public class ForeignIntoSwiftFunctionTranslationPass : SirPass<SirElement, Nothi
}
override fun transformForeignFunction(function: SirForeignFunction): SirDeclaration {
val kotlinOrigin = (function.origin as? SirOrigin.ForeignEntity)?.entity as? KotlinFunction
val kotlinOrigin = function.origin as? SirKotlinOrigin.Function
?: return function
return buildFunction {
origin = function.origin
@@ -49,12 +48,12 @@ public class ForeignIntoSwiftFunctionTranslationPass : SirPass<SirElement, Nothi
override fun run(element: SirElement, data: Nothing?): SirElement = element.transform(Transformer())
}
private fun KotlinParameter.toSir(): SirParameter = SirParameter(
private fun SirKotlinOrigin.Parameter.toSir(): SirParameter = SirParameter(
argumentName = name,
type = type.toSir(),
)
private fun KotlinType.toSir(): SirType = SirNominalType(
private fun SirKotlinOrigin.Type.toSir(): SirType = SirNominalType(
type = when (this.name) {
BYTE -> SirSwiftModule.int8
SHORT -> SirSwiftModule.int16
@@ -0,0 +1,214 @@
/*
* Copyright 2010-2023 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.sir.passes
import org.jetbrains.kotlin.sir.SirModule
import org.jetbrains.kotlin.sir.SirOrigin
import org.jetbrains.kotlin.sir.builder.buildEnum
import org.jetbrains.kotlin.sir.builder.buildForeignFunction
import org.jetbrains.kotlin.sir.builder.buildModule
import org.jetbrains.kotlin.sir.builder.buildStruct
import org.jetbrains.kotlin.sir.util.SirComparator
import org.jetbrains.kotlin.sir.util.SirPrinter
import org.jetbrains.sir.passes.SirInflatePackagesPass
import org.jetbrains.sir.passes.run
import kotlin.test.Test
class SirPackageInflaterTests {
@Test
fun `should pass on empty module`() {
fun buildModule(): SirModule = buildModule {
name = "Root"
}
val actual = buildModule()
val expected = buildModule()
val pass = SirInflatePackagesPass()
pass.run(actual)
assertEqual(expected, actual)
}
@Test
fun `should collect entities into a single flat namespace`() {
val original = buildModule {
name = "Root"
declarations += listOf(
makeFunction("com.foo"),
makeFunction("com.bar"),
makeFunction("com.baz"),
)
}
val expected = buildModule {
name = "Root"
declarations += buildEnum {
name = "com"
origin = SirOrigin.Namespace(path = listOf("com"))
declarations += listOf(
makeFunction("com.foo"),
makeFunction("com.bar"),
makeFunction("com.baz"),
)
}
}
val pass = SirInflatePackagesPass()
val actual = pass.run(original)
assertEqual(expected, actual)
}
@Test
fun `should leave other declarations alone`() {
val original = buildModule {
name = "Root"
declarations += listOf(
buildStruct { name = "Orphan" },
makeFunction("com.foo"),
makeFunction("com.bar"),
)
}
val expected = buildModule {
name = "Root"
declarations += listOf(
buildStruct { name = "Orphan" },
buildEnum {
name = "com"
origin = SirOrigin.Namespace(path = listOf("com"))
declarations += listOf(
makeFunction("com.foo"),
makeFunction("com.bar"),
)
},
)
}
val pass = SirInflatePackagesPass()
val actual = pass.run(original)
assertEqual(expected, actual)
}
@Test
fun `should collect entities into multiple namespaces`() {
val original = buildModule {
name = "Root"
declarations += listOf(
makeFunction("com.foo"),
makeFunction("org.bar"),
makeFunction("com.baz"),
)
}
val expected = buildModule {
name = "Root"
declarations += buildEnum {
name = "com"
origin = SirOrigin.Namespace(path = listOf("com"))
declarations += listOf(
makeFunction("com.foo"),
makeFunction("com.baz"),
)
}
declarations += buildEnum {
name = "org"
origin = SirOrigin.Namespace(path = listOf("org"))
declarations += listOf(
makeFunction("org.bar"),
)
}
}
val pass = SirInflatePackagesPass()
val actual = pass.run(original)
assertEqual(expected, actual)
}
@Test
fun `should collect entities into multiple nested namespaces`() {
val original = buildModule {
name = "Root"
declarations += listOf(
makeFunction("orphan"),
makeFunction("com.foo"),
makeFunction("org.bar"),
makeFunction("com.baz"),
makeFunction("org.jetbrains.baz"),
makeFunction("org.jetbrains.mascots.kotlin.kodee"),
)
}
val expected = buildModule {
name = "Root"
declarations += listOf(
makeFunction("orphan"),
buildEnum {
name = "com"
origin = SirOrigin.Namespace(path = listOf("com"))
declarations += listOf(
makeFunction("com.foo"),
makeFunction("com.baz"),
)
},
buildEnum {
name = "org"
origin = SirOrigin.Namespace(path = listOf("org"))
declarations += listOf(
makeFunction("org.bar"),
buildEnum {
name = "jetbrains"
origin = SirOrigin.Namespace(path = listOf("org", "jetbrains"))
declarations += listOf(
makeFunction("org.jetbrains.baz"),
buildEnum {
name = "mascots"
origin = SirOrigin.Namespace(path = listOf("org", "jetbrains", "mascots"))
declarations += buildEnum {
name = "kotlin"
origin = SirOrigin.Namespace(path = listOf("org", "jetbrains", "mascots", "kotlin"))
declarations += makeFunction("org.jetbrains.mascots.kotlin.kodee")
}
}
)
}
)
}
)
}
val pass = SirInflatePackagesPass()
val actual = pass.run(original)
assertEqual(expected, actual)
}
}
private fun makeFunction(fqName: String) = buildForeignFunction {
val path = fqName.split(".")
assert(path.isNotEmpty())
origin = SirOrigin.Foreign.Unknown(path)
}
private fun assertEqual(expected: SirModule, actual: SirModule) {
assert(SirComparator(options = setOf(SirComparator.Options.COMPARE_ORIGINS)).areEqual(expected, actual)) {
"\nExpected:\n\n${SirPrinter.toString(expected)}\n\nActual:\n\n${SirPrinter.toString(actual)}"
}
}
@@ -5,7 +5,10 @@
package org.jetbrains.kotlin.sir.passes
import org.jetbrains.kotlin.sir.*
import org.jetbrains.kotlin.sir.SirFunction
import org.jetbrains.kotlin.sir.SirNominalType
import org.jetbrains.kotlin.sir.SirParameter
import org.jetbrains.kotlin.sir.SirVisibility
import org.jetbrains.kotlin.sir.builder.buildForeignFunction
import org.jetbrains.kotlin.sir.builder.buildModule
import org.jetbrains.kotlin.sir.constants.*
@@ -26,12 +29,10 @@ class SirPassTests {
name = "demo"
}
val mySirElement = buildForeignFunction {
origin = SirOrigin.ForeignEntity(
MockFunction(
fqName = listOf("foo"),
parameters = emptyList(),
returnType = MockKotlinType(BOOLEAN),
)
origin = MockFunction(
fqName = listOf("foo"),
parameters = emptyList(),
returnType = MockKotlinType(BOOLEAN),
)
visibility = SirVisibility.PUBLIC
}
@@ -55,41 +56,39 @@ class SirPassTests {
name = "demo"
}
val mySirElement = buildForeignFunction {
origin = SirOrigin.ForeignEntity(
MockFunction(
fqName = listOf("foo"),
parameters = listOf(
MockParameter(
name = "arg1",
type = MockKotlinType(name = BYTE)
),
MockParameter(
name = "arg2",
type = MockKotlinType(name = SHORT)
),
MockParameter(
name = "arg3",
type = MockKotlinType(name = INT)
),
MockParameter(
name = "arg4",
type = MockKotlinType(name = LONG)
),
MockParameter(
name = "arg5",
type = MockKotlinType(name = DOUBLE)
),
MockParameter(
name = "arg6",
type = MockKotlinType(name = FLOAT)
),
MockParameter(
name = "arg7",
type = MockKotlinType(name = BOOLEAN)
)
origin = MockFunction(
fqName = listOf("foo"),
parameters = listOf(
MockParameter(
name = "arg1",
type = MockKotlinType(name = BYTE)
),
returnType = MockKotlinType(name = BYTE),
)
MockParameter(
name = "arg2",
type = MockKotlinType(name = SHORT)
),
MockParameter(
name = "arg3",
type = MockKotlinType(name = INT)
),
MockParameter(
name = "arg4",
type = MockKotlinType(name = LONG)
),
MockParameter(
name = "arg5",
type = MockKotlinType(name = DOUBLE)
),
MockParameter(
name = "arg6",
type = MockKotlinType(name = FLOAT)
),
MockParameter(
name = "arg7",
type = MockKotlinType(name = BOOLEAN)
)
),
returnType = MockKotlinType(name = BYTE),
)
visibility = SirVisibility.PUBLIC
}