[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
@@ -224,6 +224,12 @@ object FirJsExportDeclarationChecker : FirBasicDeclarationChecker(MppCheckerKind
|| isNothingOrNullableNothing
|| isPrimitiveArray
|| isNonPrimitiveArray
|| isList
|| isMutableList
|| isSet
|| isMutableSet
|| isMap
|| isMutableMap
private fun validateDeclarationOnConsumableName(
declaration: FirMemberDeclaration,
@@ -35,6 +35,13 @@ val ConeKotlinType.isNullableString: Boolean get() = isBuiltinType(StandardClass
val ConeKotlinType.isEnum: Boolean get() = isBuiltinType(StandardClassIds.Enum, false)
val ConeKotlinType.isList: Boolean get() = isBuiltinType(StandardClassIds.List, false)
val ConeKotlinType.isMutableList: Boolean get() = isBuiltinType(StandardClassIds.MutableList, false)
val ConeKotlinType.isSet: Boolean get() = isBuiltinType(StandardClassIds.Set, false)
val ConeKotlinType.isMutableSet: Boolean get() = isBuiltinType(StandardClassIds.MutableSet, false)
val ConeKotlinType.isMap: Boolean get() = isBuiltinType(StandardClassIds.Map, false)
val ConeKotlinType.isMutableMap: Boolean get() = isBuiltinType(StandardClassIds.MutableMap, false)
val ConeKotlinType.isUByte: Boolean get() = isBuiltinType(StandardClassIds.UByte, false)
val ConeKotlinType.isUShort: Boolean get() = isBuiltinType(StandardClassIds.UShort, false)
val ConeKotlinType.isUInt: Boolean get() = isBuiltinType(StandardClassIds.UInt, false)
@@ -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)
@@ -54,3 +54,27 @@ fun foo5(<!NON_EXPORTABLE_TYPE("parameter; kotlin.Function1<kotlin.Unit, kotlin.
@JsExport
fun foo6(x: (A) -> A) {
}
@JsExport
fun foo7(x: List<Int>) {
}
@JsExport
fun foo8(x: MutableList<Int>) {
}
@JsExport
fun foo9(x: Set<Int>) {
}
@JsExport
fun foo10(x: MutableSet<Int>) {
}
@JsExport
fun foo11(x: Map<String, Int>) {
}
@JsExport
fun foo12(x: MutableMap<String, Int>) {
}
@@ -54,3 +54,27 @@ fun foo5(<!NON_EXPORTABLE_TYPE("parameter; (Unit) -> Unit")!>x: (Unit) -> Unit<!
@JsExport
fun foo6(x: (A) -> A) {
}
@JsExport
fun foo7(x: List<Int>) {
}
@JsExport
fun foo8(x: MutableList<Int>) {
}
@JsExport
fun foo9(x: Set<Int>) {
}
@JsExport
fun foo10(x: MutableSet<Int>) {
}
@JsExport
fun foo11(x: Map<String, Int>) {
}
@JsExport
fun foo12(x: MutableMap<String, Int>) {
}
@@ -5,11 +5,17 @@ package foo {
@kotlin.js.JsExport public var x2: foo.C
@kotlin.js.JsExport public fun bar(): foo.C
@kotlin.js.JsExport public fun foo(/*0*/ x: foo.C): kotlin.Unit
@kotlin.js.JsExport public fun foo10(/*0*/ x: kotlin.collections.MutableSet<kotlin.Int>): kotlin.Unit
@kotlin.js.JsExport public fun foo11(/*0*/ x: kotlin.collections.Map<kotlin.String, kotlin.Int>): kotlin.Unit
@kotlin.js.JsExport public fun foo12(/*0*/ x: kotlin.collections.MutableMap<kotlin.String, kotlin.Int>): kotlin.Unit
@kotlin.js.JsExport public fun foo2(): kotlin.Unit
@kotlin.js.JsExport public fun foo3(/*0*/ x: kotlin.Unit): kotlin.Unit
@kotlin.js.JsExport public fun foo4(/*0*/ x: () -> kotlin.Unit): kotlin.Unit
@kotlin.js.JsExport public fun foo5(/*0*/ x: (kotlin.Unit) -> kotlin.Unit): kotlin.Unit
@kotlin.js.JsExport public fun foo6(/*0*/ x: (foo.A) -> foo.A): kotlin.Unit
@kotlin.js.JsExport public fun foo7(/*0*/ x: kotlin.collections.List<kotlin.Int>): kotlin.Unit
@kotlin.js.JsExport public fun foo8(/*0*/ x: kotlin.collections.MutableList<kotlin.Int>): kotlin.Unit
@kotlin.js.JsExport public fun foo9(/*0*/ x: kotlin.collections.Set<kotlin.Int>): kotlin.Unit
@kotlin.js.JsExport public final class A {
public constructor A(/*0*/ x: foo.C, /*1*/ y: foo.C)
@@ -1,3 +1,4 @@
// MUTE_LL_FIR: KT-65218
// WITH_REFLECT
// FIR_DUMP
import kotlin.reflect.*
@@ -1,3 +1,4 @@
// MUTE_LL_FIR: KT-65218
// WITH_REFLECT
// FIR_DUMP
import kotlin.reflect.*
@@ -1,6 +1,8 @@
// FIR_IDENTICAL
// KT-64271
// IGNORE_BACKEND_K2: JVM_IR
// IGNORE_BACKEND_K1: JS_IR, JS_IR_ES6
// IGNORE_BACKEND_K2: JVM_IR, JS_IR, JS_IR_ES6
// ^ Set has js specific methods
inline class IT(val x: Int)
+3 -1
View File
@@ -1,5 +1,7 @@
// WITH_STDLIB
// IGNORE_BACKEND_K2: JS_IR NATIVE
// IGNORE_BACKEND_K1: JS_IR, JS_IR_ES6
// ^ MutableList has js specific methods
// IGNORE_BACKEND_K2: JS_IR, JS_IR_ES6, NATIVE
// ^ the order of fake overrides is different on K2
interface X {
+2
View File
@@ -1,4 +1,6 @@
// WITH_STDLIB
// IGNORE_BACKEND_K1: JS_IR, JS_IR_ES6
// ^ Map has js specific methods
// IGNORE_BACKEND_K2: JS_IR
// IGNORE_BACKEND_K2: NATIVE
@@ -7,6 +7,7 @@ package org.jetbrains.kotlin.js.resolve.diagnostics
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.builtins.StandardNames
import org.jetbrains.kotlin.builtins.isFunctionType
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.descriptors.ClassKind.*
@@ -14,6 +15,7 @@ import org.jetbrains.kotlin.js.common.RESERVED_KEYWORDS
import org.jetbrains.kotlin.js.common.SPECIAL_KEYWORDS
import org.jetbrains.kotlin.js.naming.NameSuggestion
import org.jetbrains.kotlin.js.translate.utils.AnnotationsUtils
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.KtAnnotationEntry
import org.jetbrains.kotlin.psi.KtDeclaration
import org.jetbrains.kotlin.psi.KtNamedDeclaration
@@ -190,9 +192,15 @@ class JsExportDeclarationChecker(private val includeUnsignedNumbers: Boolean) :
KotlinBuiltIns.isString(nonNullable) ||
(nonNullable.isPrimitiveNumberOrNullableType() && !nonNullable.isLong()) ||
nonNullable.isNothingOrNullableNothing() ||
KotlinBuiltIns.isArray(nonNullable) ||
KotlinBuiltIns.isPrimitiveArray(nonNullable) ||
(includeUnsignedNumbers && KotlinBuiltIns.isUnsignedNumber(nonNullable))
(includeUnsignedNumbers && KotlinBuiltIns.isUnsignedNumber(nonNullable)) ||
KotlinBuiltIns.isArray(this) ||
KotlinBuiltIns.isPrimitiveArray(this) ||
KotlinBuiltIns.isConstructedFromGivenClass(this, StandardNames.FqNames.list) ||
KotlinBuiltIns.isConstructedFromGivenClass(this, StandardNames.FqNames.mutableList) ||
KotlinBuiltIns.isConstructedFromGivenClass(this, StandardNames.FqNames.set) ||
KotlinBuiltIns.isConstructedFromGivenClass(this, StandardNames.FqNames.mutableSet) ||
KotlinBuiltIns.isConstructedFromGivenClass(this, StandardNames.FqNames.map) ||
KotlinBuiltIns.isConstructedFromGivenClass(this, StandardNames.FqNames.mutableMap)
if (isPrimitiveExportableType) return true
@@ -57,6 +57,38 @@ public class IrJsES6TypeScriptExportTestGenerated extends AbstractIrJsES6TypeScr
}
}
@Nested
@TestMetadata("js/js.translator/testData/typescript-export/collections")
@TestDataPath("$PROJECT_ROOT")
public class Collections {
@Test
public void testAllFilesPresentInCollections() throws Exception {
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("js/js.translator/testData/typescript-export/collections"), Pattern.compile("^([^_](.+))\\.kt$"), null, TargetBackend.JS_IR_ES6, true);
}
@Test
@TestMetadata("collections.kt")
public void testCollections() throws Exception {
runTest("js/js.translator/testData/typescript-export/collections/collections.kt");
}
}
@Nested
@TestMetadata("js/js.translator/testData/typescript-export/collections-in-exported-file")
@TestDataPath("$PROJECT_ROOT")
public class Collections_in_exported_file {
@Test
public void testAllFilesPresentInCollections_in_exported_file() throws Exception {
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("js/js.translator/testData/typescript-export/collections-in-exported-file"), Pattern.compile("^([^_](.+))\\.kt$"), null, TargetBackend.JS_IR_ES6, true);
}
@Test
@TestMetadata("collections.kt")
public void testCollections() throws Exception {
runTest("js/js.translator/testData/typescript-export/collections-in-exported-file/collections.kt");
}
}
@Nested
@TestMetadata("js/js.translator/testData/typescript-export/constructors")
@TestDataPath("$PROJECT_ROOT")
@@ -57,6 +57,38 @@ public class IrJsTypeScriptExportTestGenerated extends AbstractIrJsTypeScriptExp
}
}
@Nested
@TestMetadata("js/js.translator/testData/typescript-export/collections")
@TestDataPath("$PROJECT_ROOT")
public class Collections {
@Test
public void testAllFilesPresentInCollections() throws Exception {
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("js/js.translator/testData/typescript-export/collections"), Pattern.compile("^([^_](.+))\\.kt$"), null, TargetBackend.JS_IR, true);
}
@Test
@TestMetadata("collections.kt")
public void testCollections() throws Exception {
runTest("js/js.translator/testData/typescript-export/collections/collections.kt");
}
}
@Nested
@TestMetadata("js/js.translator/testData/typescript-export/collections-in-exported-file")
@TestDataPath("$PROJECT_ROOT")
public class Collections_in_exported_file {
@Test
public void testAllFilesPresentInCollections_in_exported_file() throws Exception {
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("js/js.translator/testData/typescript-export/collections-in-exported-file"), Pattern.compile("^([^_](.+))\\.kt$"), null, TargetBackend.JS_IR, true);
}
@Test
@TestMetadata("collections.kt")
public void testCollections() throws Exception {
runTest("js/js.translator/testData/typescript-export/collections-in-exported-file/collections.kt");
}
}
@Nested
@TestMetadata("js/js.translator/testData/typescript-export/constructors")
@TestDataPath("$PROJECT_ROOT")
@@ -0,0 +1,53 @@
declare namespace JS_TESTS {
type Nullable<T> = T | null | undefined
namespace kotlin.collections {
interface KtList<E> /* extends kotlin.collections.Collection<E> */ {
asJsReadonlyArrayView(): ReadonlyArray<E>;
readonly __doNotUseOrImplementIt: {
readonly "kotlin.collections.KtList": unique symbol;
};
}
interface KtMap<K, V> {
asJsReadonlyMapView(): ReadonlyMap<K, V>;
readonly __doNotUseOrImplementIt: {
readonly "kotlin.collections.KtMap": unique symbol;
};
}
interface KtMutableList<E> extends kotlin.collections.KtList<E>/*, kotlin.collections.MutableCollection<E> */ {
asJsArrayView(): Array<E>;
readonly __doNotUseOrImplementIt: {
readonly "kotlin.collections.KtMutableList": unique symbol;
} & kotlin.collections.KtList<E>["__doNotUseOrImplementIt"];
}
interface KtSet<E> /* extends kotlin.collections.Collection<E> */ {
asJsReadonlySetView(): ReadonlySet<E>;
readonly __doNotUseOrImplementIt: {
readonly "kotlin.collections.KtSet": unique symbol;
};
}
interface KtMutableSet<E> extends kotlin.collections.KtSet<E>/*, kotlin.collections.MutableCollection<E> */ {
asJsSetView(): Set<E>;
readonly __doNotUseOrImplementIt: {
readonly "kotlin.collections.KtMutableSet": unique symbol;
} & kotlin.collections.KtSet<E>["__doNotUseOrImplementIt"];
}
interface KtMutableMap<K, V> extends kotlin.collections.KtMap<K, V> {
asJsMapView(): Map<K, V>;
readonly __doNotUseOrImplementIt: {
readonly "kotlin.collections.KtMutableMap": unique symbol;
} & kotlin.collections.KtMap<K, V>["__doNotUseOrImplementIt"];
}
}
function provideList(): kotlin.collections.KtList<number>;
function provideMutableList(): kotlin.collections.KtMutableList<number>;
function provideSet(): kotlin.collections.KtSet<number>;
function provideMutableSet(): kotlin.collections.KtMutableSet<number>;
function provideMap(): kotlin.collections.KtMap<string, number>;
function provideMutableMap(): kotlin.collections.KtMutableMap<string, number>;
function consumeList(list: kotlin.collections.KtList<number>): boolean;
function consumeMutableList(list: kotlin.collections.KtMutableList<number>): boolean;
function consumeSet(list: kotlin.collections.KtSet<number>): boolean;
function consumeMutableSet(list: kotlin.collections.KtMutableSet<number>): boolean;
function consumeMap(map: kotlin.collections.KtMap<string, number>): boolean;
function consumeMutableMap(map: kotlin.collections.KtMutableMap<string, number>): boolean;
}
@@ -0,0 +1,61 @@
/** This file is generated by {@link :js:js.test:generateTypeScriptJsExportOnFileTests} task. DO NOT MODIFY MANUALLY */
// CHECK_TYPESCRIPT_DECLARATIONS
// RUN_PLAIN_BOX_FUNCTION
// WITH_STDLIB
// SKIP_MINIFICATION
// SKIP_NODE_JS
// INFER_MAIN_MODULE
// TODO fix statics export in DCE-driven mode
// SKIP_DCE_DRIVEN
// MODULE: JS_TESTS
// FILE: f1.kt
@file:JsExport
fun provideList(): List<Int> = listOf(1, 2, 3)
fun provideMutableList(): MutableList<Int> = mutableListOf(4, 5, 6)
fun provideSet(): Set<Int> = setOf(1, 2, 3)
fun provideMutableSet(): MutableSet<Int> = mutableSetOf(4, 5, 6)
fun provideMap(): Map<String, Int> = mapOf("a" to 1, "b" to 2, "c" to 3)
fun provideMutableMap(): MutableMap<String, Int> = mutableMapOf("d" to 4, "e" to 5, "f" to 6)
fun consumeList(list: List<Int>) = list.toString() == "[1, 2, 3]"
fun consumeMutableList(list: MutableList<Int>): Boolean {
list.add(7)
return list.toString() == "[4, 5, 6, 7]"
}
fun consumeSet(list: Set<Int>) = list.toString() == "[1, 2, 3]"
fun consumeMutableSet(list: MutableSet<Int>): Boolean {
list.add(7)
return list.toString() == "[4, 5, 6, 7]"
}
fun consumeMap(map: Map<String, Int>) = map.toString() == "{a=1, b=2, c=3}"
fun consumeMutableMap(map: MutableMap<String, Int>): Boolean {
map["g"] = 7
return map.toString() == "{d=4, e=5, f=6, g=7}"
}
@@ -0,0 +1,213 @@
import provideList = JS_TESTS.provideList;
import consumeList = JS_TESTS.consumeList;
import provideMutableList = JS_TESTS.provideMutableList;
import consumeMutableList = JS_TESTS.consumeMutableList;
import provideMutableSet = JS_TESTS.provideMutableSet;
import provideSet = JS_TESTS.provideSet;
import consumeSet = JS_TESTS.consumeSet;
import consumeMutableSet = JS_TESTS.consumeMutableSet;
import provideMap = JS_TESTS.provideMap;
import consumeMap = JS_TESTS.consumeMap;
import provideMutableMap = JS_TESTS.provideMutableMap;
import consumeMutableMap = JS_TESTS.consumeMutableMap;
function assert(condition: boolean, message: string) {
if (!condition) {
throw message
}
}
function assertThrow(fn: () => void, message: string) {
try {
fn();
throw message;
} catch (e) {}
}
function box(): string {
testImmutableList()
testImmutableSet()
testImmutableMap()
testMutableList()
testMutableSet()
testMutableMap()
return "OK"
}
function testImmutableList() {
const list = provideList()
const listReadonlyArrayView = list.asJsReadonlyArrayView()
assert(listReadonlyArrayView[0] == 1, "Problem with accessing of element in immutable list readonly array view")
assert(listReadonlyArrayView["0"] == 1, "Problem with accessing of element in immutable list readonly array view by string")
assert(listReadonlyArrayView.map(x => x + 1).join("") == "234", "Problem with immutable list readonly array view")
assert(consumeList(list), "Problem with consumption of a Kotlin list")
assertThrow(() => { (listReadonlyArrayView as Array<number>)[1] = 4 }, "Immutable list readonly array view have ability to mutate the list by direct set")
assertThrow(() => { (listReadonlyArrayView as Array<number>).push(4) }, "Immutable list readonly array view have ability to mutate the list by 'push'")
assertThrow(() => { (listReadonlyArrayView as Array<number>).pop() }, "Immutable list readonly array view have ability to mutate the list by 'pop'")
// @ts-expect-error
assertThrow(() => { listReadonlyArrayView["foo"] }, "Immutable list getting a random index from its readonly array view")
}
function testMutableList() {
const mutableList = provideMutableList()
const mutableListReadonlyArrayView = mutableList.asJsReadonlyArrayView()
assert(mutableListReadonlyArrayView[0] == 4, "Problem with accessing of element in mutable list readonly array view")
assert(mutableListReadonlyArrayView["0"] == 4, "Problem with accessing of element in immutable list readonly array view by string")
assert(mutableListReadonlyArrayView.map(x => x + 1).join("") == "567", "Problem with mutable list readonly array view")
assert(!consumeList(mutableList), "Problem with consumption of a Kotlin mutable list as a list")
assert(consumeMutableList(mutableList), "Problem with consumption of a Kotlin mutable list as a mutable list")
assert(mutableListReadonlyArrayView.map(x => x + 1).join("") == "5678", "Problem with mutable list readonly array view after original list is mutated")
assertThrow(() => { (mutableListReadonlyArrayView as Array<number>)[1] = 4 }, "Mutable list readonly array view have ability to mutate the list by direct set")
assertThrow(() => { (mutableListReadonlyArrayView as Array<number>).push(4) }, "Mutable list readonly array view have ability to mutate the list by 'push'")
assertThrow(() => { (mutableListReadonlyArrayView as Array<number>).pop() }, "Mutable list readonly array view have ability to mutate the list by 'pop'")
// @ts-expect-error
assertThrow(() => { mutableListReadonlyArrayView["foo"] }, "Immutable list getting a random index from its readonly array view")
const mutableListArrayView = mutableList.asJsArrayView()
mutableListArrayView.pop()
assert(mutableListArrayView[0] == 4, "Problem with accessing of element in mutable list array view")
assert(mutableListArrayView["0"] == 4, "Problem with accessing of element in mutable list array view by string")
assert(mutableListArrayView.map(x => x + 1).join("") == "567", "Problem with mutable list array view")
assert(consumeMutableList(mutableList), "Problem with consumption of a Kotlin mutable list as a mutable list")
assert(mutableListArrayView.map(x => x + 1).join("") == "5678", "Problem with mutable list array view after original list is mutated")
// @ts-expect-error
assertThrow(() => { mutableListArrayView["foo"] = 4 }, "Mutable list setting a random index in its array view")
mutableListArrayView.shift()
mutableListArrayView.unshift(9)
assert(mutableListArrayView.map(x => x + 1).join("") == "10678", "Problem with mutable list array view after the view is mutated")
mutableListArrayView.sort()
assert(mutableListArrayView.map(x => x + 1).join("") == "67810", "Problem with mutable list array view after the view is mutated")
mutableListArrayView.push(3)
assert(mutableListArrayView.map(x => x + 1).join("") == "678104", "Problem with mutable list array view after the view is mutated")
mutableListArrayView[3] = 4
assert(mutableListArrayView.map(x => x + 1).join("") == "67854", "Problem with mutable list array view after the view is mutated")
mutableListArrayView["3"] = 6
assert(mutableListArrayView.map(x => x + 1).join("") == "67874", "Problem with mutable list array view after the view is mutated")
mutableListArrayView.length = 3
assert(mutableListArrayView.map(x => x + 1).join("") == "678", "Problem with mutable list array view after the view is mutated after size decreased")
assert(mutableListReadonlyArrayView.map(x => x + 1).join("") == "678", "Problem with mutable list readonly array view after size decreased")
assertThrow(() => { mutableListArrayView.length = 5 }, "Mutable list view size increasing works, but should not")
}
function testImmutableSet() {
const set = provideSet()
const setReadonlyView = set.asJsReadonlySetView();
assert(setReadonlyView.has(1), "Problem with accessing element of readonly view")
assert(joinSetOrMap(setReadonlyView) == "123", "Problem with readonly view iterator")
assert(consumeSet(set), "Problem with consumption of a Kotlin set")
assertThrow(() => { (setReadonlyView as Set<number>).add(4) }, "Set readonly view have ability to mutate the set by 'add'")
assertThrow(() => { (setReadonlyView as Set<number>).delete(4) }, "Set readonly view have ability to mutate the set by 'delete'")
assertThrow(() => { (setReadonlyView as Set<number>).clear() }, "Set readonly view have ability to mutate the set by 'clear'")
}
function testMutableSet() {
const mutableSet = provideMutableSet()
const mutableSetReadonlyView = mutableSet.asJsReadonlySetView()
assert(mutableSetReadonlyView.has(4), "Problem with accessing element of mutable set readonly view")
assert(joinSetOrMap(mutableSetReadonlyView) == "456", "Problem with mutable set readonly view iterator")
assert(!consumeSet(mutableSet), "Problem with consumption of a Kotlin mutable set as a set")
assert(consumeMutableSet(mutableSet), "Problem with consumption of a Kotlin mutable set as a mutable set")
assert(joinSetOrMap(mutableSetReadonlyView) == "4567", "Problem with mutable set readonly view after original set is mutated")
assertThrow(() => { (mutableSetReadonlyView as Set<number>).add(4) }, "Mutable set readonly view have ability to mutate the set by 'add'")
assertThrow(() => { (mutableSetReadonlyView as Set<number>).delete(4) }, "Mutable set readonly view have ability to mutate the set by 'delete'")
assertThrow(() => { (mutableSetReadonlyView as Set<number>).clear() }, "Mutable set readonly view have ability to mutate the set by 'clear'")
const mutableSetView = mutableSet.asJsSetView()
mutableSetView.delete(7)
assert(mutableSetView.has(4), "Problem with accessing element of mutable set view")
assert(joinSetOrMap(mutableSetView) == "456", "Problem with mutable set view")
assert(consumeMutableSet(mutableSet), "Problem with consumption of a Kotlin mutable set as a mutable set")
assert(joinSetOrMap(mutableSetView) == "4567", "Problem with mutable set view after original set is mutated")
mutableSetView.add(8)
assert(joinSetOrMap(mutableSetView) == "45678", "Problem with mutable set view after the view is mutated")
mutableSetView.clear()
assert(joinSetOrMap(mutableSetView) == "", "Problem with mutable set view after the view is mutated")
}
function testImmutableMap() {
const map = provideMap()
const mapReadonlyView = map.asJsReadonlyMapView()
assert(mapReadonlyView.has("a"), "Problem with accessing element in map readonly view")
assert(mapReadonlyView.get("a") == 1, "Problem with accessing element in map readonly view")
assert(joinSetOrMap(mapReadonlyView) == "[a:1][b:2][c:3]", "Problem with map readonly view iterator")
assert(consumeMap(map), "Problem with consumption of a Kotlin map")
assertThrow(() => { (mapReadonlyView as Map<string, number>).set("d", 4) }, "Map readonly view have ability to mutate the map by 'set'")
assertThrow(() => { (mapReadonlyView as Map<string, number>).delete("a") }, "Map readonly view have ability to mutate the map by 'delete'")
assertThrow(() => { (mapReadonlyView as Map<string, number>).clear() }, "Map readonly view have ability to mutate the map by 'clear'")
}
function testMutableMap() {
const mutableMap = provideMutableMap()
const mutableMapReadonlyView = mutableMap.asJsReadonlyMapView()
assert(mutableMapReadonlyView.has("d"), "Problem with accessing element in mutable map readonly view")
assert(mutableMapReadonlyView.get("d") == 4, "Problem with accessing element in mutable map readonly view")
assert(joinSetOrMap(mutableMapReadonlyView) == "[d:4][e:5][f:6]", "Problem with mutable map readonly view")
assert(!consumeMap(mutableMap), "Problem with consumption of a Kotlin mutable map as a map")
assert(consumeMutableMap(mutableMap), "Problem with consumption of a Kotlin mutable map as a mutable map")
assert(joinSetOrMap(mutableMapReadonlyView) == "[d:4][e:5][f:6][g:7]", "Problem with mutable map readonly view after original map is mutated")
assertThrow(() => { (mutableMapReadonlyView as Map<string, number>).set("d", 4) }, "Mutable map readonly view have ability to mutate the map by 'set'")
assertThrow(() => { (mutableMapReadonlyView as Map<string, number>).delete("a") }, "Mutable map readonly view have ability to mutate the map by 'delete'")
assertThrow(() => { (mutableMapReadonlyView as Map<string, number>).clear() }, "Mutable map readonly view have ability to mutate the map by 'clear'")
const mutableMapView = mutableMap.asJsMapView()
mutableMapView.delete("g")
assert(mutableMapView.has("d"), "Problem with accessing element in mutable map view")
assert(mutableMapView.get("d") == 4, "Problem with accessing element in mutable map view")
assert(joinSetOrMap(mutableMapView) == "[d:4][e:5][f:6]", "Problem with mutable map view")
assert(consumeMutableMap(mutableMap), "Problem with consumption of a Kotlin mutable map as a mutable map")
assert(joinSetOrMap(mutableMapView) == "[d:4][e:5][f:6][g:7]", "Problem with mutable map view after original map is mutated")
mutableMapView.set("h", 8)
assert(joinSetOrMap(mutableMapView) == "[d:4][e:5][f:6][g:7][h:8]", "Problem with mutable map view after the view is mutated")
mutableMapView.clear()
assert(joinSetOrMap(mutableMapView) == "", "Problem with mutable map view after the view is mutated")
}
function joinSetOrMap(setOrMap: ReadonlySet<number> | ReadonlyMap<string, number>): string {
let result = ""
if (setOrMap instanceof Set) {
setOrMap.forEach((a: any) => {
result += a
});
} else {
setOrMap.forEach((key: any, value: any) => {
result += `[${key}:${value}]`
});
}
return result
}
@@ -0,0 +1,4 @@
{
"include": [ "./*" ],
"extends": "../common.tsconfig.json"
}
@@ -0,0 +1,53 @@
declare namespace JS_TESTS {
type Nullable<T> = T | null | undefined
namespace kotlin.collections {
interface KtList<E> /* extends kotlin.collections.Collection<E> */ {
asJsReadonlyArrayView(): ReadonlyArray<E>;
readonly __doNotUseOrImplementIt: {
readonly "kotlin.collections.KtList": unique symbol;
};
}
interface KtMap<K, V> {
asJsReadonlyMapView(): ReadonlyMap<K, V>;
readonly __doNotUseOrImplementIt: {
readonly "kotlin.collections.KtMap": unique symbol;
};
}
interface KtMutableList<E> extends kotlin.collections.KtList<E>/*, kotlin.collections.MutableCollection<E> */ {
asJsArrayView(): Array<E>;
readonly __doNotUseOrImplementIt: {
readonly "kotlin.collections.KtMutableList": unique symbol;
} & kotlin.collections.KtList<E>["__doNotUseOrImplementIt"];
}
interface KtSet<E> /* extends kotlin.collections.Collection<E> */ {
asJsReadonlySetView(): ReadonlySet<E>;
readonly __doNotUseOrImplementIt: {
readonly "kotlin.collections.KtSet": unique symbol;
};
}
interface KtMutableSet<E> extends kotlin.collections.KtSet<E>/*, kotlin.collections.MutableCollection<E> */ {
asJsSetView(): Set<E>;
readonly __doNotUseOrImplementIt: {
readonly "kotlin.collections.KtMutableSet": unique symbol;
} & kotlin.collections.KtSet<E>["__doNotUseOrImplementIt"];
}
interface KtMutableMap<K, V> extends kotlin.collections.KtMap<K, V> {
asJsMapView(): Map<K, V>;
readonly __doNotUseOrImplementIt: {
readonly "kotlin.collections.KtMutableMap": unique symbol;
} & kotlin.collections.KtMap<K, V>["__doNotUseOrImplementIt"];
}
}
function provideList(): kotlin.collections.KtList<number>;
function provideMutableList(): kotlin.collections.KtMutableList<number>;
function provideSet(): kotlin.collections.KtSet<number>;
function provideMutableSet(): kotlin.collections.KtMutableSet<number>;
function provideMap(): kotlin.collections.KtMap<string, number>;
function provideMutableMap(): kotlin.collections.KtMutableMap<string, number>;
function consumeList(list: kotlin.collections.KtList<number>): boolean;
function consumeMutableList(list: kotlin.collections.KtMutableList<number>): boolean;
function consumeSet(list: kotlin.collections.KtSet<number>): boolean;
function consumeMutableSet(list: kotlin.collections.KtMutableSet<number>): boolean;
function consumeMap(map: kotlin.collections.KtMap<string, number>): boolean;
function consumeMutableMap(map: kotlin.collections.KtMutableMap<string, number>): boolean;
}
@@ -0,0 +1,57 @@
// CHECK_TYPESCRIPT_DECLARATIONS
// RUN_PLAIN_BOX_FUNCTION
// WITH_STDLIB
// SKIP_MINIFICATION
// SKIP_NODE_JS
// INFER_MAIN_MODULE
// TODO fix statics export in DCE-driven mode
// SKIP_DCE_DRIVEN
// MODULE: JS_TESTS
// FILE: f1.kt
@JsExport
fun provideList(): List<Int> = listOf(1, 2, 3)
@JsExport
fun provideMutableList(): MutableList<Int> = mutableListOf(4, 5, 6)
@JsExport
fun provideSet(): Set<Int> = setOf(1, 2, 3)
@JsExport
fun provideMutableSet(): MutableSet<Int> = mutableSetOf(4, 5, 6)
@JsExport
fun provideMap(): Map<String, Int> = mapOf("a" to 1, "b" to 2, "c" to 3)
@JsExport
fun provideMutableMap(): MutableMap<String, Int> = mutableMapOf("d" to 4, "e" to 5, "f" to 6)
@JsExport
fun consumeList(list: List<Int>) = list.toString() == "[1, 2, 3]"
@JsExport
fun consumeMutableList(list: MutableList<Int>): Boolean {
list.add(7)
return list.toString() == "[4, 5, 6, 7]"
}
@JsExport
fun consumeSet(list: Set<Int>) = list.toString() == "[1, 2, 3]"
@JsExport
fun consumeMutableSet(list: MutableSet<Int>): Boolean {
list.add(7)
return list.toString() == "[4, 5, 6, 7]"
}
@JsExport
fun consumeMap(map: Map<String, Int>) = map.toString() == "{a=1, b=2, c=3}"
@JsExport
fun consumeMutableMap(map: MutableMap<String, Int>): Boolean {
map["g"] = 7
return map.toString() == "{d=4, e=5, f=6, g=7}"
}
@@ -0,0 +1,213 @@
import provideList = JS_TESTS.provideList;
import consumeList = JS_TESTS.consumeList;
import provideMutableList = JS_TESTS.provideMutableList;
import consumeMutableList = JS_TESTS.consumeMutableList;
import provideMutableSet = JS_TESTS.provideMutableSet;
import provideSet = JS_TESTS.provideSet;
import consumeSet = JS_TESTS.consumeSet;
import consumeMutableSet = JS_TESTS.consumeMutableSet;
import provideMap = JS_TESTS.provideMap;
import consumeMap = JS_TESTS.consumeMap;
import provideMutableMap = JS_TESTS.provideMutableMap;
import consumeMutableMap = JS_TESTS.consumeMutableMap;
function assert(condition: boolean, message: string) {
if (!condition) {
throw message
}
}
function assertThrow(fn: () => void, message: string) {
try {
fn();
throw message;
} catch (e) {}
}
function box(): string {
testImmutableList()
testImmutableSet()
testImmutableMap()
testMutableList()
testMutableSet()
testMutableMap()
return "OK"
}
function testImmutableList() {
const list = provideList()
const listReadonlyArrayView = list.asJsReadonlyArrayView()
assert(listReadonlyArrayView[0] == 1, "Problem with accessing of element in immutable list readonly array view")
assert(listReadonlyArrayView["0"] == 1, "Problem with accessing of element in immutable list readonly array view by string")
assert(listReadonlyArrayView.map(x => x + 1).join("") == "234", "Problem with immutable list readonly array view")
assert(consumeList(list), "Problem with consumption of a Kotlin list")
assertThrow(() => { (listReadonlyArrayView as Array<number>)[1] = 4 }, "Immutable list readonly array view have ability to mutate the list by direct set")
assertThrow(() => { (listReadonlyArrayView as Array<number>).push(4) }, "Immutable list readonly array view have ability to mutate the list by 'push'")
assertThrow(() => { (listReadonlyArrayView as Array<number>).pop() }, "Immutable list readonly array view have ability to mutate the list by 'pop'")
// @ts-expect-error
assertThrow(() => { listReadonlyArrayView["foo"] }, "Immutable list getting a random index from its readonly array view")
}
function testMutableList() {
const mutableList = provideMutableList()
const mutableListReadonlyArrayView = mutableList.asJsReadonlyArrayView()
assert(mutableListReadonlyArrayView[0] == 4, "Problem with accessing of element in mutable list readonly array view")
assert(mutableListReadonlyArrayView["0"] == 4, "Problem with accessing of element in immutable list readonly array view by string")
assert(mutableListReadonlyArrayView.map(x => x + 1).join("") == "567", "Problem with mutable list readonly array view")
assert(!consumeList(mutableList), "Problem with consumption of a Kotlin mutable list as a list")
assert(consumeMutableList(mutableList), "Problem with consumption of a Kotlin mutable list as a mutable list")
assert(mutableListReadonlyArrayView.map(x => x + 1).join("") == "5678", "Problem with mutable list readonly array view after original list is mutated")
assertThrow(() => { (mutableListReadonlyArrayView as Array<number>)[1] = 4 }, "Mutable list readonly array view have ability to mutate the list by direct set")
assertThrow(() => { (mutableListReadonlyArrayView as Array<number>).push(4) }, "Mutable list readonly array view have ability to mutate the list by 'push'")
assertThrow(() => { (mutableListReadonlyArrayView as Array<number>).pop() }, "Mutable list readonly array view have ability to mutate the list by 'pop'")
// @ts-expect-error
assertThrow(() => { mutableListReadonlyArrayView["foo"] }, "Immutable list getting a random index from its readonly array view")
const mutableListArrayView = mutableList.asJsArrayView()
mutableListArrayView.pop()
assert(mutableListArrayView[0] == 4, "Problem with accessing of element in mutable list array view")
assert(mutableListArrayView["0"] == 4, "Problem with accessing of element in mutable list array view by string")
assert(mutableListArrayView.map(x => x + 1).join("") == "567", "Problem with mutable list array view")
assert(consumeMutableList(mutableList), "Problem with consumption of a Kotlin mutable list as a mutable list")
assert(mutableListArrayView.map(x => x + 1).join("") == "5678", "Problem with mutable list array view after original list is mutated")
// @ts-expect-error
assertThrow(() => { mutableListArrayView["foo"] = 4 }, "Mutable list setting a random index in its array view")
mutableListArrayView.shift()
mutableListArrayView.unshift(9)
assert(mutableListArrayView.map(x => x + 1).join("") == "10678", "Problem with mutable list array view after the view is mutated")
mutableListArrayView.sort()
assert(mutableListArrayView.map(x => x + 1).join("") == "67810", "Problem with mutable list array view after the view is mutated")
mutableListArrayView.push(3)
assert(mutableListArrayView.map(x => x + 1).join("") == "678104", "Problem with mutable list array view after the view is mutated")
mutableListArrayView[3] = 4
assert(mutableListArrayView.map(x => x + 1).join("") == "67854", "Problem with mutable list array view after the view is mutated")
mutableListArrayView["3"] = 6
assert(mutableListArrayView.map(x => x + 1).join("") == "67874", "Problem with mutable list array view after the view is mutated")
mutableListArrayView.length = 3
assert(mutableListArrayView.map(x => x + 1).join("") == "678", "Problem with mutable list array view after the view is mutated after size decreased")
assert(mutableListReadonlyArrayView.map(x => x + 1).join("") == "678", "Problem with mutable list readonly array view after size decreased")
assertThrow(() => { mutableListArrayView.length = 5 }, "Mutable list view size increasing works, but should not")
}
function testImmutableSet() {
const set = provideSet()
const setReadonlyView = set.asJsReadonlySetView();
assert(setReadonlyView.has(1), "Problem with accessing element of readonly view")
assert(joinSetOrMap(setReadonlyView) == "123", "Problem with readonly view iterator")
assert(consumeSet(set), "Problem with consumption of a Kotlin set")
assertThrow(() => { (setReadonlyView as Set<number>).add(4) }, "Set readonly view have ability to mutate the set by 'add'")
assertThrow(() => { (setReadonlyView as Set<number>).delete(4) }, "Set readonly view have ability to mutate the set by 'delete'")
assertThrow(() => { (setReadonlyView as Set<number>).clear() }, "Set readonly view have ability to mutate the set by 'clear'")
}
function testMutableSet() {
const mutableSet = provideMutableSet()
const mutableSetReadonlyView = mutableSet.asJsReadonlySetView()
assert(mutableSetReadonlyView.has(4), "Problem with accessing element of mutable set readonly view")
assert(joinSetOrMap(mutableSetReadonlyView) == "456", "Problem with mutable set readonly view iterator")
assert(!consumeSet(mutableSet), "Problem with consumption of a Kotlin mutable set as a set")
assert(consumeMutableSet(mutableSet), "Problem with consumption of a Kotlin mutable set as a mutable set")
assert(joinSetOrMap(mutableSetReadonlyView) == "4567", "Problem with mutable set readonly view after original set is mutated")
assertThrow(() => { (mutableSetReadonlyView as Set<number>).add(4) }, "Mutable set readonly view have ability to mutate the set by 'add'")
assertThrow(() => { (mutableSetReadonlyView as Set<number>).delete(4) }, "Mutable set readonly view have ability to mutate the set by 'delete'")
assertThrow(() => { (mutableSetReadonlyView as Set<number>).clear() }, "Mutable set readonly view have ability to mutate the set by 'clear'")
const mutableSetView = mutableSet.asJsSetView()
mutableSetView.delete(7)
assert(mutableSetView.has(4), "Problem with accessing element of mutable set view")
assert(joinSetOrMap(mutableSetView) == "456", "Problem with mutable set view")
assert(consumeMutableSet(mutableSet), "Problem with consumption of a Kotlin mutable set as a mutable set")
assert(joinSetOrMap(mutableSetView) == "4567", "Problem with mutable set view after original set is mutated")
mutableSetView.add(8)
assert(joinSetOrMap(mutableSetView) == "45678", "Problem with mutable set view after the view is mutated")
mutableSetView.clear()
assert(joinSetOrMap(mutableSetView) == "", "Problem with mutable set view after the view is mutated")
}
function testImmutableMap() {
const map = provideMap()
const mapReadonlyView = map.asJsReadonlyMapView()
assert(mapReadonlyView.has("a"), "Problem with accessing element in map readonly view")
assert(mapReadonlyView.get("a") == 1, "Problem with accessing element in map readonly view")
assert(joinSetOrMap(mapReadonlyView) == "[a:1][b:2][c:3]", "Problem with map readonly view iterator")
assert(consumeMap(map), "Problem with consumption of a Kotlin map")
assertThrow(() => { (mapReadonlyView as Map<string, number>).set("d", 4) }, "Map readonly view have ability to mutate the map by 'set'")
assertThrow(() => { (mapReadonlyView as Map<string, number>).delete("a") }, "Map readonly view have ability to mutate the map by 'delete'")
assertThrow(() => { (mapReadonlyView as Map<string, number>).clear() }, "Map readonly view have ability to mutate the map by 'clear'")
}
function testMutableMap() {
const mutableMap = provideMutableMap()
const mutableMapReadonlyView = mutableMap.asJsReadonlyMapView()
assert(mutableMapReadonlyView.has("d"), "Problem with accessing element in mutable map readonly view")
assert(mutableMapReadonlyView.get("d") == 4, "Problem with accessing element in mutable map readonly view")
assert(joinSetOrMap(mutableMapReadonlyView) == "[d:4][e:5][f:6]", "Problem with mutable map readonly view")
assert(!consumeMap(mutableMap), "Problem with consumption of a Kotlin mutable map as a map")
assert(consumeMutableMap(mutableMap), "Problem with consumption of a Kotlin mutable map as a mutable map")
assert(joinSetOrMap(mutableMapReadonlyView) == "[d:4][e:5][f:6][g:7]", "Problem with mutable map readonly view after original map is mutated")
assertThrow(() => { (mutableMapReadonlyView as Map<string, number>).set("d", 4) }, "Mutable map readonly view have ability to mutate the map by 'set'")
assertThrow(() => { (mutableMapReadonlyView as Map<string, number>).delete("a") }, "Mutable map readonly view have ability to mutate the map by 'delete'")
assertThrow(() => { (mutableMapReadonlyView as Map<string, number>).clear() }, "Mutable map readonly view have ability to mutate the map by 'clear'")
const mutableMapView = mutableMap.asJsMapView()
mutableMapView.delete("g")
assert(mutableMapView.has("d"), "Problem with accessing element in mutable map view")
assert(mutableMapView.get("d") == 4, "Problem with accessing element in mutable map view")
assert(joinSetOrMap(mutableMapView) == "[d:4][e:5][f:6]", "Problem with mutable map view")
assert(consumeMutableMap(mutableMap), "Problem with consumption of a Kotlin mutable map as a mutable map")
assert(joinSetOrMap(mutableMapView) == "[d:4][e:5][f:6][g:7]", "Problem with mutable map view after original map is mutated")
mutableMapView.set("h", 8)
assert(joinSetOrMap(mutableMapView) == "[d:4][e:5][f:6][g:7][h:8]", "Problem with mutable map view after the view is mutated")
mutableMapView.clear()
assert(joinSetOrMap(mutableMapView) == "", "Problem with mutable map view after the view is mutated")
}
function joinSetOrMap(setOrMap: ReadonlySet<number> | ReadonlyMap<string, number>): string {
let result = ""
if (setOrMap instanceof Set) {
setOrMap.forEach((a: any) => {
result += a
});
} else {
setOrMap.forEach((key: any, value: any) => {
result += `[${key}:${value}]`
});
}
return result
}
@@ -0,0 +1,4 @@
{
"include": [ "./*" ],
"extends": "../common.tsconfig.json"
}
@@ -2,6 +2,7 @@
"compilerOptions": {
"target": "es5",
"strict": true,
"newLine": "lf"
"newLine": "lf",
"lib": ["es2015", "dom"]
}
}
@@ -1,5 +1,13 @@
declare namespace JS_TESTS {
type Nullable<T> = T | null | undefined
namespace kotlin.collections {
interface KtList<E> /* extends kotlin.collections.Collection<E> */ {
asJsReadonlyArrayView(): ReadonlyArray<E>;
readonly __doNotUseOrImplementIt: {
readonly "kotlin.collections.KtList": unique symbol;
};
}
}
namespace foo {
interface SomeExternalInterface {
}
@@ -18,8 +26,8 @@ declare namespace JS_TESTS {
function genericWithMultipleConstraints<T extends unknown/* kotlin.Comparable<T> */ & foo.SomeExternalInterface & Error>(x: T): T;
function generic3<A, B, C, D, E>(a: A, b: B, c: C, d: D): Nullable<E>;
function inlineFun(x: number, callback: (p0: number) => void): void;
function formatList(value: any/* kotlin.collections.List<UnknownType *> */): string;
function createList(): any/* kotlin.collections.List<UnknownType *> */;
function formatList(value: kotlin.collections.KtList<any /*UnknownType **/>): string;
function createList(): kotlin.collections.KtList<any /*UnknownType **/>;
function defaultParametersAtTheBegining(a: string | undefined, b: string): string;
function nonDefaultParameterInBetween(a: string | undefined, b: string, c?: string): string;
}
@@ -1,5 +1,13 @@
declare namespace JS_TESTS {
type Nullable<T> = T | null | undefined
namespace kotlin.collections {
interface KtList<E> /* extends kotlin.collections.Collection<E> */ {
asJsReadonlyArrayView(): ReadonlyArray<E>;
readonly __doNotUseOrImplementIt: {
readonly "kotlin.collections.KtList": unique symbol;
};
}
}
namespace foo {
interface SomeExternalInterface {
}
@@ -18,8 +26,8 @@ declare namespace JS_TESTS {
function genericWithMultipleConstraints<T extends unknown/* kotlin.Comparable<T> */ & foo.SomeExternalInterface & Error>(x: T): T;
function generic3<A, B, C, D, E>(a: A, b: B, c: C, d: D): Nullable<E>;
function inlineFun(x: number, callback: (p0: number) => void): void;
function formatList(value: any/* kotlin.collections.List<UnknownType *> */): string;
function createList(): any/* kotlin.collections.List<UnknownType *> */;
function formatList(value: kotlin.collections.KtList<any /*UnknownType **/>): string;
function createList(): kotlin.collections.KtList<any /*UnknownType **/>;
function defaultParametersAtTheBegining(a: string | undefined, b: string): string;
function nonDefaultParameterInBetween(a: string | undefined, b: string, c?: string): string;
}
+36 -1
View File
@@ -10111,6 +10111,11 @@ public open class ArrayList<E> : kotlin.collections.AbstractMutableList<E>, kotl
public open override fun addAll(elements: kotlin.collections.Collection<E>): kotlin.Boolean
@kotlin.js.ExperimentalJsExport
@kotlin.js.ExperimentalJsCollectionsApi
@kotlin.SinceKotlin(version = "1.9")
public open override fun asJsArrayView(): kotlin.js.collections.JsArray<E>
public open override fun clear(): kotlin.Unit
public final fun ensureCapacity(minCapacity: kotlin.Int): kotlin.Unit
@@ -10310,6 +10315,11 @@ public open class LinkedHashSet<E> : kotlin.collections.HashSet<E>, kotlin.colle
public interface List<out E> : kotlin.collections.Collection<E> {
public abstract override val size: kotlin.Int { get; }
@kotlin.js.ExperimentalJsExport
@kotlin.js.ExperimentalJsCollectionsApi
@kotlin.SinceKotlin(version = "1.9")
public open fun asJsReadonlyArrayView(): kotlin.js.collections.JsReadonlyArray<E>
public abstract override operator fun contains(element: E): kotlin.Boolean
public abstract override fun containsAll(elements: kotlin.collections.Collection<E>): kotlin.Boolean
@@ -10362,6 +10372,11 @@ public interface Map<K, out V> {
public abstract val values: kotlin.collections.Collection<V> { get; }
@kotlin.js.ExperimentalJsExport
@kotlin.js.ExperimentalJsCollectionsApi
@kotlin.SinceKotlin(version = "1.9")
public open fun asJsReadonlyMapView(): kotlin.js.collections.JsReadonlyMap<K, V>
public abstract fun containsKey(key: K): kotlin.Boolean
public abstract fun containsValue(value: V): kotlin.Boolean
@@ -10410,6 +10425,11 @@ public interface MutableList<E> : kotlin.collections.List<E>, kotlin.collections
public abstract override fun addAll(elements: kotlin.collections.Collection<E>): kotlin.Boolean
@kotlin.js.ExperimentalJsExport
@kotlin.js.ExperimentalJsCollectionsApi
@kotlin.SinceKotlin(version = "1.9")
public open fun asJsArrayView(): kotlin.js.collections.JsArray<E>
public abstract override fun clear(): kotlin.Unit
public abstract override fun listIterator(): kotlin.collections.MutableListIterator<E>
@@ -10448,6 +10468,11 @@ public interface MutableMap<K, V> : kotlin.collections.Map<K, V> {
public abstract override val values: kotlin.collections.MutableCollection<V> { get; }
@kotlin.js.ExperimentalJsExport
@kotlin.js.ExperimentalJsCollectionsApi
@kotlin.SinceKotlin(version = "1.9")
public open fun asJsMapView(): kotlin.js.collections.JsMap<K, V>
public abstract fun clear(): kotlin.Unit
public abstract fun put(key: K, value: V): V?
@@ -10466,6 +10491,11 @@ public interface MutableSet<E> : kotlin.collections.Set<E>, kotlin.collections.M
public abstract override fun addAll(elements: kotlin.collections.Collection<E>): kotlin.Boolean
@kotlin.js.ExperimentalJsExport
@kotlin.js.ExperimentalJsCollectionsApi
@kotlin.SinceKotlin(version = "1.9")
public open fun asJsSetView(): kotlin.js.collections.JsSet<E>
public abstract override fun clear(): kotlin.Unit
public abstract override operator fun iterator(): kotlin.collections.MutableIterator<E>
@@ -10483,6 +10513,11 @@ public interface RandomAccess {
public interface Set<out E> : kotlin.collections.Collection<E> {
public abstract override val size: kotlin.Int { get; }
@kotlin.js.ExperimentalJsExport
@kotlin.js.ExperimentalJsCollectionsApi
@kotlin.SinceKotlin(version = "1.9")
public open fun asJsReadonlySetView(): kotlin.js.collections.JsReadonlySet<E>
public abstract override operator fun contains(element: E): kotlin.Boolean
public abstract override fun containsAll(elements: kotlin.collections.Collection<E>): kotlin.Boolean
@@ -10498,4 +10533,4 @@ public abstract class ShortIterator : kotlin.collections.Iterator<kotlin.Short>
public final override operator fun next(): kotlin.Short
public abstract fun nextShort(): kotlin.Short
}
}
@@ -0,0 +1,38 @@
@kotlin.js.JsName(name = "Array")
@kotlin.SinceKotlin(version = "1.9")
@kotlin.js.ExperimentalJsCollectionsApi
public open external class JsArray<E> : kotlin.js.collections.JsReadonlyArray<E> {
public constructor JsArray<E>()
}
@kotlin.js.JsName(name = "Map")
@kotlin.SinceKotlin(version = "1.9")
@kotlin.js.ExperimentalJsCollectionsApi
public open external class JsMap<K, V> : kotlin.js.collections.JsReadonlyMap<K, V> {
public constructor JsMap<K, V>()
}
@kotlin.js.JsName(name = "ReadonlyArray")
@kotlin.SinceKotlin(version = "1.9")
@kotlin.js.ExperimentalJsCollectionsApi
public external interface JsReadonlyArray<out E> {
}
@kotlin.js.JsName(name = "ReadonlyMap")
@kotlin.SinceKotlin(version = "1.9")
@kotlin.js.ExperimentalJsCollectionsApi
public external interface JsReadonlyMap<K, out V> {
}
@kotlin.js.JsName(name = "ReadonlySet")
@kotlin.SinceKotlin(version = "1.9")
@kotlin.js.ExperimentalJsCollectionsApi
public external interface JsReadonlySet<out E> {
}
@kotlin.js.JsName(name = "Set")
@kotlin.SinceKotlin(version = "1.9")
@kotlin.js.ExperimentalJsCollectionsApi
public open external class JsSet<E> : kotlin.js.collections.JsReadonlySet<E> {
public constructor JsSet<E>()
}
+9
View File
@@ -201,6 +201,15 @@ public final annotation class EagerInitialization : kotlin.Annotation {
public constructor EagerInitialization()
}
@kotlin.RequiresOptIn(level = Level.WARNING)
@kotlin.annotation.Retention(value = AnnotationRetention.BINARY)
@kotlin.annotation.Target(allowedTargets = {AnnotationTarget.CLASS, AnnotationTarget.FUNCTION})
@kotlin.annotation.MustBeDocumented
@kotlin.SinceKotlin(version = "1.9")
public final annotation class ExperimentalJsCollectionsApi : kotlin.Annotation {
public constructor ExperimentalJsCollectionsApi()
}
@kotlin.RequiresOptIn(level = Level.WARNING)
@kotlin.annotation.MustBeDocumented
@kotlin.annotation.Retention(value = AnnotationRetention.BINARY)
@@ -130,4 +130,19 @@ public expect annotation class JsExport() {
)
@MustBeDocumented
@SinceKotlin("1.9")
public annotation class ExperimentalJsReflectionCreateInstance
public annotation class ExperimentalJsReflectionCreateInstance
/**
* This annotation marks the experimental JS-collections API that allows to manipulate with native JS-collections
* The API can be removed completely in any further release.
*
* Any usage of a declaration annotated with `@ExperimentalJsCollectionsApi` should be accepted either by
* annotating that usage with the [OptIn] annotation, e.g. `@OptIn(ExperimentalJsCollectionsApi::class)`,
* or by using the compiler argument `-opt-in=kotlin.js.ExperimentalJsCollectionsApi`.
*/
@RequiresOptIn(level = RequiresOptIn.Level.WARNING)
@Retention(AnnotationRetention.BINARY)
@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION)
@MustBeDocumented
@SinceKotlin("1.9")
public annotation class ExperimentalJsCollectionsApi
@@ -28,6 +28,8 @@
package kotlin.collections
import kotlin.js.collections.*
/**
* Classes that inherit from this interface can be represented as a sequence of elements that can
* be iterated over.
@@ -146,8 +148,11 @@ public interface List<out E> : Collection<E> {
// Query Operations
override val size: Int
override fun isEmpty(): Boolean
override fun contains(element: @UnsafeVariance E): Boolean
override fun iterator(): Iterator<E>
// Bulk Operations
@@ -191,6 +196,15 @@ public interface List<out E> : Collection<E> {
* Structural changes in the base list make the behavior of the view undefined.
*/
public fun subList(fromIndex: Int, toIndex: Int): List<E>
/**
* Returns a view with the [JsReadonlyArray] methods to consume it in JavaScript as a regular readonly array.
* Structural changes in the base list are synchronized with the view.
*/
@ExperimentalJsExport
@ExperimentalJsCollectionsApi
@SinceKotlin("1.9")
public fun asJsReadonlyArrayView(): JsReadonlyArray<E> = createJsReadonlyArrayViewFrom(this)
}
/**
@@ -226,7 +240,9 @@ public interface MutableList<E> : List<E>, MutableCollection<E> {
public fun addAll(index: Int, elements: Collection<E>): Boolean
override fun removeAll(elements: Collection<E>): Boolean
override fun retainAll(elements: Collection<E>): Boolean
override fun clear(): Unit
// Positional Access Operations
@@ -256,6 +272,15 @@ public interface MutableList<E> : List<E>, MutableCollection<E> {
// View
override fun subList(fromIndex: Int, toIndex: Int): MutableList<E>
/**
* Returns a view with the [JsArray] methods to consume it in JavaScript as a regular array.
* Structural changes in the base list are synchronized with the view, and vice verse.
*/
@ExperimentalJsExport
@ExperimentalJsCollectionsApi
@SinceKotlin("1.9")
public fun asJsArrayView(): JsArray<E> = createJsArrayViewFrom(this)
}
/**
@@ -268,12 +293,24 @@ public interface Set<out E> : Collection<E> {
// Query Operations
override val size: Int
override fun isEmpty(): Boolean
override fun contains(element: @UnsafeVariance E): Boolean
override fun iterator(): Iterator<E>
// Bulk Operations
override fun containsAll(elements: Collection<@UnsafeVariance E>): Boolean
/**
* Returns a view with the [JsReadonlySet] methods to consume it in JavaScript as a regular readonly Set.
* Structural changes in the base set are synchronized with the view.
*/
@ExperimentalJsExport
@ExperimentalJsCollectionsApi
@SinceKotlin("1.9")
public fun asJsReadonlySetView(): JsReadonlySet<E> = createJsReadonlySetViewFrom(this)
}
/**
@@ -299,9 +336,21 @@ public interface MutableSet<E> : Set<E>, MutableCollection<E> {
// Bulk Modification Operations
override fun addAll(elements: Collection<E>): Boolean
override fun removeAll(elements: Collection<E>): Boolean
override fun retainAll(elements: Collection<E>): Boolean
override fun clear(): Unit
/**
* Returns a view with the [JsSet] methods to consume it in JavaScript as a regular Set.
* Structural changes in the base set are synchronized with the view, and vice verse.
*/
@ExperimentalJsExport
@ExperimentalJsCollectionsApi
@SinceKotlin("1.9")
public fun asJsSetView(): JsSet<E> = createJsSetViewFrom(this)
}
/**
@@ -370,6 +419,15 @@ public interface Map<K, out V> {
*/
public val value: V
}
/**
* Returns a view with the [JsReadonlyMap] methods to consume it in JavaScript as a regular readonly Map.
* Structural changes in the base map are synchronized with the view.
*/
@ExperimentalJsExport
@ExperimentalJsCollectionsApi
@SinceKotlin("1.9")
public fun asJsReadonlyMapView(): JsReadonlyMap<K, V> = createJsReadonlyMapViewFrom(this)
}
/**
@@ -432,4 +490,13 @@ public interface MutableMap<K, V> : Map<K, V> {
*/
public fun setValue(newValue: V): V
}
/**
* Returns a view with the [JsMap] methods to consume it in JavaScript as a regular Map.
* Structural changes in the base map are synchronized with the view, and vice verse.
*/
@ExperimentalJsExport
@ExperimentalJsCollectionsApi
@SinceKotlin("1.9")
public fun asJsMapView(): JsMap<K, V> = createJsMapViewFrom(this)
}
@@ -0,0 +1,220 @@
/*
* 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.
*/
@file:OptIn(ExperimentalJsCollectionsApi::class)
package kotlin.collections
import kotlin.js.collections.*
private class JsArrayView<E> : JsArray<E>()
private fun UNSUPPORTED_OPERATION() {
throw UnsupportedOperationException()
}
internal fun <E> createJsReadonlyArrayViewFrom(list: List<E>): JsReadonlyArray<E> =
createJsArrayViewWith(
listSize = { list.size },
listGet = { i -> list[i] },
listSet = ::UNSUPPORTED_OPERATION.asDynamic(),
listDecreaseSize = ::UNSUPPORTED_OPERATION.asDynamic(),
listIncreaseSize = ::UNSUPPORTED_OPERATION.asDynamic()
)
internal fun <E> createJsArrayViewFrom(list: MutableList<E>): JsArray<E> =
createJsArrayViewWith(
listSize = { list.size },
listGet = { i -> list[i] },
listSet = { i, v -> list[i] = v },
listDecreaseSize = { size -> list.subList(list.size - size, list.size).clear() },
listIncreaseSize = ::UNSUPPORTED_OPERATION.asDynamic()
)
@Suppress("UNUSED_VARIABLE", "UNUSED_PARAMETER")
private fun <E> createJsArrayViewWith(
listSize: () -> Int,
listGet: (Int) -> E,
listSet: (Int, E) -> Unit,
listDecreaseSize: (Int) -> Unit,
listIncreaseSize: (Int) -> Unit,
): dynamic {
val arrayView = objectCreate<JsArrayView<*>>()
return js("""
new Proxy(arrayView, {
get: function(target, prop, receiver) {
if (prop === "length") return listSize();
var type = typeof prop
var index = type === "string" || type === "number" ? +prop : undefined
if (!isNaN(index)) return listGet(index);
return target[prop]
},
has: function(target, key) { return !isNaN(key) && key < listSize() },
set: function(obj, prop, value) {
if (prop === "length") {
var size = listSize();
var newSize = type === "string" || type === "number" ? +prop : undefined
if (isNaN(newSize)) throw new RangeError("invalid array length")
if (newSize < size) listDecreaseSize(size - newSize)
else listIncreaseSize(newSize - size)
return true
}
var type = typeof prop;
var index = type === "string" || type === "number" ? +prop : undefined;
if (isNaN(index)) return false;
listSet(index, value)
return true
},
})
""")
}
private class JsSetView<E> : JsSet<E>()
internal fun <E> createJsReadonlySetViewFrom(set: Set<E>): JsReadonlySet<E> =
createJsSetViewWith<E>(
setSize = { set.size },
setAdd = ::UNSUPPORTED_OPERATION.asDynamic(),
setRemove = ::UNSUPPORTED_OPERATION.asDynamic(),
setClear = ::UNSUPPORTED_OPERATION.asDynamic(),
setContains = { v -> set.contains(v) },
valuesIterator = { createJsIteratorFrom(set.iterator()) },
entriesIterator = { createJsIteratorFrom(set.iterator()) { arrayOf(it, it) } },
forEach = { cb, t -> forEach(cb, t) }
)
internal fun <E> createJsSetViewFrom(set: MutableSet<E>): JsSet<E> =
createJsSetViewWith<E>(
setSize = { set.size },
setAdd = { v -> set.add(v) },
setRemove = { v -> set.remove(v) },
setClear = { set.clear() },
setContains = { v -> set.contains(v) },
valuesIterator = { createJsIteratorFrom(set.iterator()) },
entriesIterator = { createJsIteratorFrom(set.iterator()) { arrayOf(it, it) } },
forEach = { cb, t -> forEach(cb, t) }
)
@Suppress("UNUSED_VARIABLE", "UNUSED_PARAMETER")
private fun <E> createJsSetViewWith(
setSize: () -> Int,
setAdd: (E) -> Unit,
setRemove: (E) -> Boolean,
setClear: () -> Unit,
setContains: (E) -> Boolean,
valuesIterator: () -> dynamic,
entriesIterator: () -> dynamic,
forEach: (dynamic, dynamic) -> Unit,
): dynamic {
val setView = objectCreate<JsSetView<E>>().also {
js("it[Symbol.iterator] = valuesIterator")
defineProp(it, "size", setSize, VOID)
}
return js("""
Object.assign(setView, {
add: function(value) { setAdd(value); return this },
'delete': setRemove,
clear: setClear,
has: setContains,
keys: valuesIterator,
values: valuesIterator,
entries: entriesIterator,
forEach: function (cb, thisArg) { forEach(cb, thisArg || setView) }
})
""")
}
private class JsMapView<K, V> : JsMap<K, V>()
internal fun <K, V> createJsReadonlyMapViewFrom(map: Map<K, V>): JsReadonlyMap<K, V> =
createJsMapViewWith<K, V>(
mapSize = { map.size },
mapGet = { k -> map[k] },
mapContains = { k -> map.containsKey(k) },
mapPut = ::UNSUPPORTED_OPERATION.asDynamic(),
mapRemove = ::UNSUPPORTED_OPERATION.asDynamic(),
mapClear = ::UNSUPPORTED_OPERATION.asDynamic(),
keysIterator = { createJsIteratorFrom(map.keys.iterator()) },
valuesIterator = { createJsIteratorFrom(map.values.iterator()) },
entriesIterator = { createJsIteratorFrom(map.entries.iterator()) { arrayOf(it.key, it.value) } },
forEach = { cb, t -> forEach(cb, t) }
)
internal fun <K, V> createJsMapViewFrom(map: MutableMap<K, V>): JsMap<K, V> =
createJsMapViewWith<K, V>(
mapSize = { map.size },
mapGet = { k -> map[k] },
mapContains = { k -> map.containsKey(k) },
mapPut = { k, v -> map.put(k, v) },
mapRemove = { k -> map.remove(k) },
mapClear = { map.clear() },
keysIterator = { createJsIteratorFrom(map.keys.iterator()) },
valuesIterator = { createJsIteratorFrom(map.values.iterator()) },
entriesIterator = { createJsIteratorFrom(map.entries.iterator()) { arrayOf(it.key, it.value) } },
forEach = { cb, t -> forEach(cb, t) }
)
@Suppress("UNUSED_VARIABLE", "UNUSED_PARAMETER")
private fun <K, V> createJsMapViewWith(
mapSize: () -> Int,
mapGet: (K) -> V?,
mapContains: (K) -> Boolean,
mapPut: (K, V) -> Unit,
mapRemove: (K) -> Unit,
mapClear: () -> Unit,
keysIterator: () -> dynamic,
valuesIterator: () -> dynamic,
entriesIterator: () -> dynamic,
forEach: (dynamic, dynamic) -> Unit,
): dynamic {
val mapView = objectCreate<JsMapView<K, V>>().also {
js("it[Symbol.iterator] = entriesIterator")
defineProp(it, "size", mapSize, VOID)
}
return js("""
Object.assign(mapView, {
get: mapGet,
set: function(key, value) { mapPut(key, value); return this },
'delete': mapRemove,
clear: mapClear,
has: mapContains,
keys: valuesIterator,
values: valuesIterator,
entries: entriesIterator,
forEach: function (cb, thisArg) { forEach(cb, thisArg || mapView) }
})
""")
}
@Suppress("UNUSED_VARIABLE", "UNUSED_PARAMETER")
private fun <T> createJsIteratorFrom(iterator: Iterator<T>, transform: (T) -> dynamic = { it }): dynamic {
val iteratorNext = { iterator.next() }
val iteratorHasNext = { iterator.hasNext() }
return js("""{
next: function() {
var result = { done: !iteratorHasNext() };
if (!result.done) result.value = transform(iteratorNext());
return result;
}
}""")
}
private fun forEach(cb: (dynamic, dynamic, dynamic) -> Unit, thisArg: dynamic) {
val iterator = thisArg.entries()
var result = iterator.next()
while (!result.done) {
val value = result.value
cb(value[0], value[1], thisArg)
result = iterator.next()
}
}
+1 -1
View File
@@ -208,7 +208,7 @@ internal fun protoOf(constructor: Any) =
js("constructor.prototype")
@Suppress("UNUSED_PARAMETER")
internal fun <T> objectCreate(proto: T?) =
internal fun <T> objectCreate(proto: T? = null): T =
js("Object.create(proto)")
@Suppress("UNUSED_PARAMETER")
+8 -7
View File
@@ -44,16 +44,17 @@ internal fun safePropertySet(self: dynamic, setterName: String, propName: String
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER)
internal annotation class JsFun(val code: String)
/**
* The annotation is needed for annotating class declarations and type alias which are used inside exported declarations, but
* doesn't contain @JsExport annotation
* This information is used for generating special tagged types inside d.ts files, for more strict usage of implicitly exported entities
*/
@Target(AnnotationTarget.CLASS)
internal annotation class JsImplicitExport
/**
* The annotation is needed for annotating function declarations that should be compiled as ES6 generators
*/
@Target(AnnotationTarget.FUNCTION)
internal annotation class JsGenerator
/**
* The annotation is needed for annotating class declarations and type alias which are used inside exported declarations, but
* doesn't contain @JsExport annotation
* This information is used for generating special tagged types inside d.ts files, for more strict usage of implicitly exported entities
*/
@Target(AnnotationTarget.CLASS)
internal annotation class JsImplicitExport(val couldBeConvertedToExplicitExport: Boolean)
@@ -7,6 +7,8 @@
package kotlin.collections
import kotlin.js.collections.JsArray
/**
* Provides a [MutableList] implementation, which uses a resizable array as its backing storage.
*
@@ -178,6 +180,10 @@ public actual open class ArrayList<E> internal constructor(private var array: Ar
return js("[]").slice.call(array)
}
@ExperimentalJsExport
@ExperimentalJsCollectionsApi
@SinceKotlin("1.9")
override fun asJsArrayView(): JsArray<E> = array.unsafeCast<JsArray<E>>()
internal override fun checkIsMutable() {
if (isReadOnly) throw UnsupportedOperationException()
@@ -0,0 +1,55 @@
/*
* 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 kotlin.js.collections
/**
* Exposes the TypeScript [ReadonlyArray](https://www.typescriptlang.org/docs/handbook/2/objects.html#the-readonlyarray-type) to Kotlin.
*/
@JsName("ReadonlyArray")
@SinceKotlin("1.9")
@ExperimentalJsCollectionsApi
public external interface JsReadonlyArray<out E>
/**
* Exposes the JavaScript [Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array) to Kotlin.
*/
@JsName("Array")
@SinceKotlin("1.9")
@ExperimentalJsCollectionsApi
public external open class JsArray<E> : JsReadonlyArray<E>
/**
* Exposes the TypeScript [ReadonlySet](https://github.com/microsoft/TypeScript/blob/bd952a7a83ce04b3541b952238b6c0e4316b7d5d/src/lib/es2015.collection.d.ts#L103) to Kotlin.
*/
@JsName("ReadonlySet")
@SinceKotlin("1.9")
@ExperimentalJsCollectionsApi
public external interface JsReadonlySet<out E>
/**
* Exposes the JavaScript [Set](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set) to Kotlin.
*/
@JsName("Set")
@SinceKotlin("1.9")
@ExperimentalJsCollectionsApi
public external open class JsSet<E> : JsReadonlySet<E>
/**
* Exposes the TypeScript [ReadonlyMap](https://github.com/microsoft/TypeScript/blob/bd952a7a83ce04b3541b952238b6c0e4316b7d5d/src/lib/es2015.collection.d.ts#L37) to Kotlin.
*/
@JsName("ReadonlyMap")
@SinceKotlin("1.9")
@ExperimentalJsCollectionsApi
public external interface JsReadonlyMap<K, out V>
/**
* Exposes the JavaScript [Map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) to Kotlin.
*/
@JsName("Map")
@SinceKotlin("1.9")
@ExperimentalJsCollectionsApi
public external open class JsMap<K, V> : JsReadonlyMap<K, V>
@@ -3384,6 +3384,9 @@ public final class kotlin/jdk7/AutoCloseableKt {
public static final fun closeFinally (Ljava/lang/AutoCloseable;Ljava/lang/Throwable;)V
}
public abstract interface annotation class kotlin/js/ExperimentalJsCollectionsApi : java/lang/annotation/Annotation {
}
public abstract interface annotation class kotlin/js/ExperimentalJsExport : java/lang/annotation/Annotation {
}