[K/JS] Support essential Kotlin collections (List, MutableList, Set, MutableSet, Map, MutableMap) for exporting into JS

^KT-34995 Fixed
^KT-44871 Fixed
This commit is contained in:
Artem Kobzar
2024-01-24 11:14:46 +00:00
committed by Space Team
parent b71797383f
commit 8d1a90c23c
42 changed files with 1431 additions and 47 deletions
@@ -349,13 +349,10 @@ class JsIntrinsics(private val irBuiltIns: IrBuiltIns, val context: JsIrBackendC
context.symbolTable.descriptorExtension.referenceClass(context.getJsInternalClass("DoNotIntrinsify"))
val jsFunAnnotationSymbol = context.symbolTable.descriptorExtension.referenceClass(context.getJsInternalClass("JsFun"))
val jsNameAnnotationSymbol = context.symbolTable.descriptorExtension.referenceClass(context.getJsInternalClass("JsName"))
val jsExportAnnotationSymbol = context.symbolTable.descriptorExtension.referenceClass(context.getJsInternalClass("JsExport"))
val jsGeneratorAnnotationSymbol = context.symbolTable.descriptorExtension.referenceClass(context.getJsInternalClass("JsGenerator"))
val jsExportAnnotationSymbol by lazy(LazyThreadSafetyMode.NONE) {
context.symbolTable.descriptorExtension.referenceClass(context.getJsInternalClass("JsExport"))
}
val jsExportIgnoreAnnotationSymbol by lazy(LazyThreadSafetyMode.NONE) {
val jsExportIgnoreAnnotationSymbol by context.lazy2 {
jsExportAnnotationSymbol.owner
.findDeclaration<IrClass> { it.fqNameWhenAvailable == FqName("kotlin.js.JsExport.Ignore") }
?.symbol ?: error("can't find kotlin.js.JsExport.Ignore annotation")
@@ -46,6 +46,12 @@ private val collectClassDefaultConstructorsPhase = makeIrModulePhase(
description = "Collect classes default constructors to add it to metadata on code generating phase"
)
private val prepareCollectionsToExportLowering = makeIrModulePhase(
::PrepareCollectionsToExportLowering,
name = "PrepareCollectionsToExportLowering",
description = "Add @JsImplicitExport to exportable collections all the declarations which we don't want to export such as `Enum.entries` or `DataClass::componentN`",
)
private val preventExportOfSyntheticDeclarationsLowering = makeIrModulePhase(
::ExcludeSyntheticDeclarationsFromExportLowering,
name = "ExcludeSyntheticDeclarationsFromExportLowering",
@@ -746,7 +752,7 @@ private val escapedIdentifiersLowering = makeIrModulePhase(
private val implicitlyExportedDeclarationsMarkingLowering = makeIrModulePhase(
::ImplicitlyExportedDeclarationsMarkingLowering,
name = "ImplicitlyExportedDeclarationsMarkingLowering",
description = "Add @JsImplicitExport annotation to declarations which are not exported but are used inside other exported declarations as a type"
description = "Add @JsImplicitExport annotation to declarations which are not exported but are used inside other exported declarations as a type",
)
private val cleanupLoweringPhase = makeIrModulePhase<JsIrBackendContext>(
@@ -782,6 +788,7 @@ val mainFunctionCallWrapperLowering = makeIrModulePhase<JsIrBackendContext>(
val loweringList = listOf<SimpleNamedCompilerPhase<JsIrBackendContext, IrModuleFragment, IrModuleFragment>>(
scriptRemoveReceiverLowering,
validateIrBeforeLowering,
prepareCollectionsToExportLowering,
preventExportOfSyntheticDeclarationsLowering,
inventNamesForLocalClassesPhase,
collectClassIdentifiersLowering,
@@ -10,9 +10,7 @@ import org.jetbrains.kotlin.backend.common.phaser.PhaseConfig
import org.jetbrains.kotlin.backend.common.phaser.PhaserState
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.ir.IrBuiltIns
import org.jetbrains.kotlin.ir.backend.js.lower.collectNativeImplementations
import org.jetbrains.kotlin.ir.backend.js.lower.generateJsTests
import org.jetbrains.kotlin.ir.backend.js.lower.moveBodilessDeclarationsToSeparatePlace
import org.jetbrains.kotlin.ir.backend.js.lower.*
import org.jetbrains.kotlin.ir.backend.js.lower.serialization.ir.JsIrLinker
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.CompilationOutputs
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.JsGenerationGranularity
@@ -34,7 +34,10 @@ class ExportModelGenerator(val context: JsIrBackendContext, val generateNamespac
fun generateExport(file: IrPackageFragment): List<ExportedDeclaration> {
val namespaceFqName = file.packageFqName
val exports = file.declarations.memoryOptimizedFlatMap { declaration -> listOfNotNull(exportDeclaration(declaration)) }
val exports = file.declarations.memoryOptimizedMapNotNull { declaration ->
declaration.takeIf { it.couldBeConvertedToExplicitExport() != true }?.let(::exportDeclaration)
}
return when {
exports.isEmpty() -> emptyList()
!generateNamespacesForPackages || namespaceFqName.isRoot -> exports
@@ -306,6 +309,7 @@ class ExportModelGenerator(val context: JsIrBackendContext, val generateNamespac
val candidate = getExportCandidate(declaration) ?: continue
if (isImplicitlyExportedClass && candidate !is IrClass) continue
if (!shouldDeclarationBeExportedImplicitlyOrExplicitly(candidate, context)) continue
if (candidate.isFakeOverride && klass.isInterface) continue
val processingResult = specialProcessing(candidate)
if (processingResult != null) {
@@ -734,15 +738,15 @@ private fun shouldDeclarationBeExported(declaration: IrDeclarationWithName, cont
if (declaration is IrClass && declaration.kind == ClassKind.ENUM_ENTRY)
return false
if (declaration.isJsExportIgnore())
return false
if (context.additionalExportedDeclarationNames.contains(declaration.fqNameWhenAvailable))
return true
if (context.additionalExportedDeclarations.contains(declaration))
return true
if (declaration.isJsExportIgnore())
return false
if (declaration is IrOverridableDeclaration<*>) {
val overriddenNonEmpty = declaration
.overriddenSymbols
@@ -770,7 +774,7 @@ fun IrOverridableDeclaration<*>.isAllowedFakeOverriddenDeclaration(context: JsIr
resolveFakeOverrideMaybeAbstract { it === this || it.parentClassOrNull?.isExported(context) != true }
}
if (firstExportedRealOverride?.parentClassOrNull.isExportedInterface(context)) {
if (firstExportedRealOverride?.parentClassOrNull.isExportedInterface(context) && firstExportedRealOverride?.isJsExportIgnore() != true) {
return true
}
@@ -9,7 +9,9 @@ import org.jetbrains.kotlin.backend.common.DeclarationTransformer
import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext
import org.jetbrains.kotlin.ir.backend.js.export.isExported
import org.jetbrains.kotlin.ir.backend.js.ir.JsIrBuilder
import org.jetbrains.kotlin.ir.backend.js.utils.isJsImplicitExport
import org.jetbrains.kotlin.ir.backend.js.lazy2
import org.jetbrains.kotlin.ir.backend.js.utils.JsAnnotations
import org.jetbrains.kotlin.ir.backend.js.utils.couldBeConvertedToExplicitExport
import org.jetbrains.kotlin.ir.declarations.IrClass
import org.jetbrains.kotlin.ir.declarations.IrDeclaration
import org.jetbrains.kotlin.ir.declarations.IrFunction
@@ -17,17 +19,18 @@ import org.jetbrains.kotlin.ir.declarations.IrProperty
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
import org.jetbrains.kotlin.ir.symbols.IrTypeParameterSymbol
import org.jetbrains.kotlin.ir.types.*
import org.jetbrains.kotlin.ir.util.constructors
import org.jetbrains.kotlin.ir.util.isPrimitiveArray
import org.jetbrains.kotlin.ir.util.parentClassOrNull
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.js.config.JSConfigurationKeys
import org.jetbrains.kotlin.utils.memoryOptimizedMap
import org.jetbrains.kotlin.utils.memoryOptimizedPlus
class ImplicitlyExportedDeclarationsMarkingLowering(private val context: JsIrBackendContext) : DeclarationTransformer {
private val strictImplicitExport = context.configuration.getBoolean(JSConfigurationKeys.GENERATE_STRICT_IMPLICIT_EXPORT)
private val jsExportCtor by context.lazy2 { context.intrinsics.jsExportAnnotationSymbol.constructors.single() }
private val jsImplicitExportCtor by context.lazy2 { context.intrinsics.jsImplicitExportAnnotationSymbol.constructors.single() }
override fun transformFlat(declaration: IrDeclaration): List<IrDeclaration>? {
if (!strictImplicitExport || !declaration.isExported(context)) return null
if (!declaration.isExported(context)) return null
val implicitlyExportedDeclarations = when (declaration) {
is IrFunction -> declaration.collectImplicitlyExportedDeclarations()
@@ -36,7 +39,7 @@ class ImplicitlyExportedDeclarationsMarkingLowering(private val context: JsIrBac
else -> emptySet()
}
implicitlyExportedDeclarations.forEach { it.markWithJsImplicitExport() }
implicitlyExportedDeclarations.forEach { it.markWithJsImplicitExportOrUpgrade() }
return null
}
@@ -80,19 +83,28 @@ class ImplicitlyExportedDeclarationsMarkingLowering(private val context: JsIrBac
classifier is IrTypeParameterSymbol -> classifier.owner.superTypes.flatMap { it.collectImplicitlyExportedDeclarations() }
.toSet()
classifier is IrClassSymbol -> setOfNotNull(classifier.owner.takeIf { it.shouldBeMarkedWithImplicitExport() })
classifier is IrClassSymbol -> setOfNotNull(classifier.owner.takeIf { it.shouldBeMarkedWithImplicitExportOrUpgraded() })
else -> emptySet()
}
}
private fun IrDeclaration.shouldBeMarkedWithImplicitExport(): Boolean {
return this is IrClass && !isExternal && !isExported(context) && !isJsImplicitExport()
private fun IrDeclaration.shouldBeMarkedWithImplicitExportOrUpgraded(): Boolean {
return this is IrClass && !isExternal && !isExported(context)
}
private fun IrDeclaration.markWithJsImplicitExport() {
val jsImplicitExportCtor = context.intrinsics.jsImplicitExportAnnotationSymbol.constructors.single()
annotations = annotations memoryOptimizedPlus JsIrBuilder.buildConstructorCall(jsImplicitExportCtor)
private fun IrDeclaration.markWithJsImplicitExportOrUpgrade() {
if (couldBeConvertedToExplicitExport() == true) {
annotations = annotations.memoryOptimizedMap {
if (it.isAnnotation(JsAnnotations.jsImplicitExportFqn)) {
JsIrBuilder.buildConstructorCall(jsExportCtor)
} else it
}
} else if (strictImplicitExport) {
annotations = annotations memoryOptimizedPlus JsIrBuilder.buildConstructorCall(jsImplicitExportCtor).apply {
putValueArgument(0, false.toIrConst(context.irBuiltIns.booleanType))
}
parentClassOrNull?.takeIf { it.shouldBeMarkedWithImplicitExport() }?.markWithJsImplicitExport()
parentClassOrNull?.takeIf { it.shouldBeMarkedWithImplicitExportOrUpgraded() }?.markWithJsImplicitExportOrUpgrade()
}
}
}
@@ -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.kotlin.ir.backend.js.lower
import org.jetbrains.kotlin.backend.common.DeclarationTransformer
import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext
import org.jetbrains.kotlin.ir.backend.js.ir.JsIrBuilder
import org.jetbrains.kotlin.ir.declarations.IrClass
import org.jetbrains.kotlin.ir.declarations.IrDeclaration
import org.jetbrains.kotlin.ir.declarations.IrDeclarationWithName
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
import org.jetbrains.kotlin.ir.symbols.IrConstructorSymbol
import org.jetbrains.kotlin.ir.util.isFakeOverride
import org.jetbrains.kotlin.ir.util.parentClassOrNull
import org.jetbrains.kotlin.ir.util.primaryConstructor
import org.jetbrains.kotlin.ir.util.toIrConst
import org.jetbrains.kotlin.utils.memoryOptimizedPlus
// TODO: Remove the lowering and move annotations into stdlib after solving problem with tests on KLIB
class PrepareCollectionsToExportLowering(private val context: JsIrBackendContext) : DeclarationTransformer {
private val jsNameCtor by lazy(LazyThreadSafetyMode.NONE) {
context.intrinsics.jsNameAnnotationSymbol.primaryConstructorSymbol
}
private val jsExportIgnoreCtor by lazy(LazyThreadSafetyMode.NONE) {
context.intrinsics.jsExportIgnoreAnnotationSymbol.primaryConstructorSymbol
}
private val jsImplicitExportCtor by lazy(LazyThreadSafetyMode.NONE) {
context.intrinsics.jsImplicitExportAnnotationSymbol.primaryConstructorSymbol
}
private val IrClassSymbol.primaryConstructorSymbol: IrConstructorSymbol get() = owner.primaryConstructor!!.symbol
private val exportedMethodNames = setOf(
"asJsReadonlyArrayView",
"asJsArrayView",
"asJsReadonlySetView",
"asJsSetView",
"asJsReadonlyMapView",
"asJsMapView"
)
private val exportableSymbols = setOf(
context.ir.symbols.list,
context.ir.symbols.mutableList,
context.ir.symbols.set,
context.ir.symbols.mutableSet,
context.ir.symbols.map,
context.ir.symbols.mutableMap,
)
override fun transformFlat(declaration: IrDeclaration): List<IrDeclaration>? {
if (declaration is IrClass && declaration.symbol in exportableSymbols) {
declaration.addJsName()
declaration.markWithJsImplicitExport()
declaration.declarations.forEach {
if (it !is IrDeclarationWithName || it.name.toString() !in exportedMethodNames) {
it.excludeFromJsExport()
}
}
}
return null
}
private fun IrDeclaration.excludeFromJsExport() {
if (this is IrSimpleFunction) {
correspondingPropertySymbol?.owner?.excludeFromJsExport()
}
annotations = annotations memoryOptimizedPlus JsIrBuilder.buildConstructorCall(jsExportIgnoreCtor)
}
private fun IrDeclarationWithName.addJsName() {
annotations = annotations memoryOptimizedPlus JsIrBuilder.buildConstructorCall(jsNameCtor).apply {
putValueArgument(0, "Kt${name.asString()}".toIrConst(context.irBuiltIns.stringType))
}
}
private fun IrDeclaration.markWithJsImplicitExport() {
annotations = annotations memoryOptimizedPlus JsIrBuilder.buildConstructorCall(jsImplicitExportCtor).apply {
putValueArgument(0, true.toIrConst(context.irBuiltIns.booleanType))
}
}
}
@@ -36,6 +36,10 @@ object JsAnnotations {
fun IrConstructorCall.getSingleConstStringArgument() =
(getValueArgument(0) as IrConst<String>).value
@Suppress("UNCHECKED_CAST")
fun IrConstructorCall.getSingleConstBooleanArgument() =
(getValueArgument(0) as IrConst<Boolean>).value
fun IrAnnotationContainer.getJsModule(): String? =
getAnnotation(JsAnnotations.jsModuleFqn)?.getSingleConstStringArgument()
@@ -63,6 +67,9 @@ fun IrAnnotationContainer.isJsExport(): Boolean =
fun IrAnnotationContainer.isJsImplicitExport(): Boolean =
hasAnnotation(JsAnnotations.jsImplicitExportFqn)
fun IrAnnotationContainer.couldBeConvertedToExplicitExport(): Boolean? =
getAnnotation(JsAnnotations.jsImplicitExportFqn)?.getSingleConstBooleanArgument()
fun IrAnnotationContainer.isJsExportIgnore(): Boolean =
hasAnnotation(JsAnnotations.jsExportIgnoreFqn)