From 8d1a90c23cbd4e04747e1e9815047bb54a987e83 Mon Sep 17 00:00:00 2001 From: Artem Kobzar Date: Wed, 24 Jan 2024 11:14:46 +0000 Subject: [PATCH] [K/JS] Support essential Kotlin collections (List, MutableList, Set, MutableSet, Map, MutableMap) for exporting into JS ^KT-34995 Fixed ^KT-44871 Fixed --- .../FirJsExportDeclarationChecker.kt | 6 + .../kotlin/fir/types/ConeBuiltinTypeUtils.kt | 7 + .../kotlin/ir/backend/js/JsIntrinsics.kt | 7 +- .../kotlin/ir/backend/js/JsLoweringPhases.kt | 9 +- .../kotlin/ir/backend/js/compiler.kt | 4 +- .../backend/js/export/ExportModelGenerator.kt | 14 +- ...itlyExportedDeclarationsMarkingLowering.kt | 38 +-- .../PrepareCollectionsToExportLowering.kt | 88 +++++++ .../ir/backend/js/utils/AnnotationUtils.kt | 7 + .../unexportableTypesInSignature.fir.kt | 24 ++ .../export/unexportableTypesInSignature.kt | 24 ++ .../export/unexportableTypesInSignature.txt | 6 + .../unsafeAssignmentExtra.fir.kt | 1 + .../builderInference/unsafeAssignmentExtra.kt | 1 + .../inlineCollectionOfInlineClass.kt | 4 +- .../testData/ir/irText/expressions/kt30020.kt | 4 +- .../testData/ir/irText/firProblems/kt43342.kt | 2 + .../diagnostics/JsExportDeclarationChecker.kt | 14 +- .../IrJsES6TypeScriptExportTestGenerated.java | 32 +++ .../ir/IrJsTypeScriptExportTestGenerated.java | 32 +++ .../collections.d.ts | 53 +++++ .../collections.kt | 61 +++++ .../collections__main.ts | 213 +++++++++++++++++ .../tsconfig.json | 4 + .../collections/collections.d.ts | 53 +++++ .../collections/collections.kt | 57 +++++ .../collections/collections__main.ts | 213 +++++++++++++++++ .../collections/tsconfig.json | 4 + .../typescript-export/common.tsconfig.json | 3 +- .../functions-in-exported-file/functions.d.ts | 12 +- .../functions/functions.d.ts | 12 +- libraries/stdlib/api/js/kotlin.collections.kt | 37 ++- .../stdlib/api/js/kotlin.js.collections.kt | 38 +++ libraries/stdlib/api/js/kotlin.js.kt | 9 + .../common/src/kotlin/JsAnnotationsH.kt | 17 +- libraries/stdlib/js/builtins/Collections.kt | 67 ++++++ .../stdlib/js/runtime/collectionsInterop.kt | 220 ++++++++++++++++++ libraries/stdlib/js/runtime/coreRuntime.kt | 2 +- libraries/stdlib/js/runtime/kotlinJsHacks.kt | 15 +- .../js/src/kotlin/collections/ArrayList.kt | 6 + .../stdlib/js/src/kotlin/js.collections.kt | 55 +++++ .../kotlin-stdlib-runtime-merged.txt | 3 + 42 files changed, 1431 insertions(+), 47 deletions(-) create mode 100644 compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/PrepareCollectionsToExportLowering.kt create mode 100644 js/js.translator/testData/typescript-export/collections-in-exported-file/collections.d.ts create mode 100644 js/js.translator/testData/typescript-export/collections-in-exported-file/collections.kt create mode 100644 js/js.translator/testData/typescript-export/collections-in-exported-file/collections__main.ts create mode 100644 js/js.translator/testData/typescript-export/collections-in-exported-file/tsconfig.json create mode 100644 js/js.translator/testData/typescript-export/collections/collections.d.ts create mode 100644 js/js.translator/testData/typescript-export/collections/collections.kt create mode 100644 js/js.translator/testData/typescript-export/collections/collections__main.ts create mode 100644 js/js.translator/testData/typescript-export/collections/tsconfig.json create mode 100644 libraries/stdlib/api/js/kotlin.js.collections.kt create mode 100644 libraries/stdlib/js/runtime/collectionsInterop.kt create mode 100644 libraries/stdlib/js/src/kotlin/js.collections.kt diff --git a/compiler/fir/checkers/checkers.js/src/org/jetbrains/kotlin/fir/analysis/js/checkers/declaration/FirJsExportDeclarationChecker.kt b/compiler/fir/checkers/checkers.js/src/org/jetbrains/kotlin/fir/analysis/js/checkers/declaration/FirJsExportDeclarationChecker.kt index 0226e7622aa..3faf498336b 100644 --- a/compiler/fir/checkers/checkers.js/src/org/jetbrains/kotlin/fir/analysis/js/checkers/declaration/FirJsExportDeclarationChecker.kt +++ b/compiler/fir/checkers/checkers.js/src/org/jetbrains/kotlin/fir/analysis/js/checkers/declaration/FirJsExportDeclarationChecker.kt @@ -224,6 +224,12 @@ object FirJsExportDeclarationChecker : FirBasicDeclarationChecker(MppCheckerKind || isNothingOrNullableNothing || isPrimitiveArray || isNonPrimitiveArray + || isList + || isMutableList + || isSet + || isMutableSet + || isMap + || isMutableMap private fun validateDeclarationOnConsumableName( declaration: FirMemberDeclaration, diff --git a/compiler/fir/cones/src/org/jetbrains/kotlin/fir/types/ConeBuiltinTypeUtils.kt b/compiler/fir/cones/src/org/jetbrains/kotlin/fir/types/ConeBuiltinTypeUtils.kt index a0de51c95c5..198b5f37b8e 100644 --- a/compiler/fir/cones/src/org/jetbrains/kotlin/fir/types/ConeBuiltinTypeUtils.kt +++ b/compiler/fir/cones/src/org/jetbrains/kotlin/fir/types/ConeBuiltinTypeUtils.kt @@ -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) diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsIntrinsics.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsIntrinsics.kt index 659e1dadd96..4db4cef301e 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsIntrinsics.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsIntrinsics.kt @@ -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 { it.fqNameWhenAvailable == FqName("kotlin.js.JsExport.Ignore") } ?.symbol ?: error("can't find kotlin.js.JsExport.Ignore annotation") diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsLoweringPhases.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsLoweringPhases.kt index cf0b35f3eb0..2d0a4c2da4a 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsLoweringPhases.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsLoweringPhases.kt @@ -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( @@ -782,6 +788,7 @@ val mainFunctionCallWrapperLowering = makeIrModulePhase( val loweringList = listOf>( scriptRemoveReceiverLowering, validateIrBeforeLowering, + prepareCollectionsToExportLowering, preventExportOfSyntheticDeclarationsLowering, inventNamesForLocalClassesPhase, collectClassIdentifiersLowering, diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/compiler.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/compiler.kt index 2b047bd014c..570dcd1f94d 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/compiler.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/compiler.kt @@ -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 diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModelGenerator.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModelGenerator.kt index 602cdb48f1e..44b9eaeee92 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModelGenerator.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModelGenerator.kt @@ -34,7 +34,10 @@ class ExportModelGenerator(val context: JsIrBackendContext, val generateNamespac fun generateExport(file: IrPackageFragment): List { 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 } diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/ImplicitlyExportedDeclarationsMarkingLowering.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/ImplicitlyExportedDeclarationsMarkingLowering.kt index 54feb07197a..be1e5d13475 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/ImplicitlyExportedDeclarationsMarkingLowering.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/ImplicitlyExportedDeclarationsMarkingLowering.kt @@ -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? { - 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() + } } } \ No newline at end of file diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/PrepareCollectionsToExportLowering.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/PrepareCollectionsToExportLowering.kt new file mode 100644 index 00000000000..d66f2eef228 --- /dev/null +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/PrepareCollectionsToExportLowering.kt @@ -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? { + 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)) + } + } +} \ No newline at end of file diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/AnnotationUtils.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/AnnotationUtils.kt index e5f66635c12..15d8591a068 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/AnnotationUtils.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/AnnotationUtils.kt @@ -36,6 +36,10 @@ object JsAnnotations { fun IrConstructorCall.getSingleConstStringArgument() = (getValueArgument(0) as IrConst).value +@Suppress("UNCHECKED_CAST") +fun IrConstructorCall.getSingleConstBooleanArgument() = + (getValueArgument(0) as IrConst).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) diff --git a/compiler/testData/diagnostics/testsWithJsStdLib/export/unexportableTypesInSignature.fir.kt b/compiler/testData/diagnostics/testsWithJsStdLib/export/unexportableTypesInSignature.fir.kt index 79089129cbf..da76f2df5e7 100644 --- a/compiler/testData/diagnostics/testsWithJsStdLib/export/unexportableTypesInSignature.fir.kt +++ b/compiler/testData/diagnostics/testsWithJsStdLib/export/unexportableTypesInSignature.fir.kt @@ -54,3 +54,27 @@ fun foo5( A) { } + +@JsExport +fun foo7(x: List) { +} + +@JsExport +fun foo8(x: MutableList) { +} + +@JsExport +fun foo9(x: Set) { +} + +@JsExport +fun foo10(x: MutableSet) { +} + +@JsExport +fun foo11(x: Map) { +} + +@JsExport +fun foo12(x: MutableMap) { +} diff --git a/compiler/testData/diagnostics/testsWithJsStdLib/export/unexportableTypesInSignature.kt b/compiler/testData/diagnostics/testsWithJsStdLib/export/unexportableTypesInSignature.kt index 80bd0a2713a..96748dcf8c2 100644 --- a/compiler/testData/diagnostics/testsWithJsStdLib/export/unexportableTypesInSignature.kt +++ b/compiler/testData/diagnostics/testsWithJsStdLib/export/unexportableTypesInSignature.kt @@ -54,3 +54,27 @@ fun foo5( Unit")!>x: (Unit) -> Unit A) { } + +@JsExport +fun foo7(x: List) { +} + +@JsExport +fun foo8(x: MutableList) { +} + +@JsExport +fun foo9(x: Set) { +} + +@JsExport +fun foo10(x: MutableSet) { +} + +@JsExport +fun foo11(x: Map) { +} + +@JsExport +fun foo12(x: MutableMap) { +} diff --git a/compiler/testData/diagnostics/testsWithJsStdLib/export/unexportableTypesInSignature.txt b/compiler/testData/diagnostics/testsWithJsStdLib/export/unexportableTypesInSignature.txt index b6454f8e59d..1e98ee6b341 100644 --- a/compiler/testData/diagnostics/testsWithJsStdLib/export/unexportableTypesInSignature.txt +++ b/compiler/testData/diagnostics/testsWithJsStdLib/export/unexportableTypesInSignature.txt @@ -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.Unit + @kotlin.js.JsExport public fun foo11(/*0*/ x: kotlin.collections.Map): kotlin.Unit + @kotlin.js.JsExport public fun foo12(/*0*/ x: kotlin.collections.MutableMap): 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.Unit + @kotlin.js.JsExport public fun foo8(/*0*/ x: kotlin.collections.MutableList): kotlin.Unit + @kotlin.js.JsExport public fun foo9(/*0*/ x: kotlin.collections.Set): kotlin.Unit @kotlin.js.JsExport public final class A { public constructor A(/*0*/ x: foo.C, /*1*/ y: foo.C) diff --git a/compiler/testData/diagnostics/testsWithStdLib/builderInference/unsafeAssignmentExtra.fir.kt b/compiler/testData/diagnostics/testsWithStdLib/builderInference/unsafeAssignmentExtra.fir.kt index 9752dd56719..711e8f39e74 100644 --- a/compiler/testData/diagnostics/testsWithStdLib/builderInference/unsafeAssignmentExtra.fir.kt +++ b/compiler/testData/diagnostics/testsWithStdLib/builderInference/unsafeAssignmentExtra.fir.kt @@ -1,3 +1,4 @@ +// MUTE_LL_FIR: KT-65218 // WITH_REFLECT // FIR_DUMP import kotlin.reflect.* diff --git a/compiler/testData/diagnostics/testsWithStdLib/builderInference/unsafeAssignmentExtra.kt b/compiler/testData/diagnostics/testsWithStdLib/builderInference/unsafeAssignmentExtra.kt index e33c3c36680..08a08ab3588 100644 --- a/compiler/testData/diagnostics/testsWithStdLib/builderInference/unsafeAssignmentExtra.kt +++ b/compiler/testData/diagnostics/testsWithStdLib/builderInference/unsafeAssignmentExtra.kt @@ -1,3 +1,4 @@ +// MUTE_LL_FIR: KT-65218 // WITH_REFLECT // FIR_DUMP import kotlin.reflect.* diff --git a/compiler/testData/ir/irText/declarations/inlineCollectionOfInlineClass.kt b/compiler/testData/ir/irText/declarations/inlineCollectionOfInlineClass.kt index 930b3b1928b..01c81ff332e 100644 --- a/compiler/testData/ir/irText/declarations/inlineCollectionOfInlineClass.kt +++ b/compiler/testData/ir/irText/declarations/inlineCollectionOfInlineClass.kt @@ -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) diff --git a/compiler/testData/ir/irText/expressions/kt30020.kt b/compiler/testData/ir/irText/expressions/kt30020.kt index c9d3c647056..4a4bc6cd28c 100644 --- a/compiler/testData/ir/irText/expressions/kt30020.kt +++ b/compiler/testData/ir/irText/expressions/kt30020.kt @@ -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 { diff --git a/compiler/testData/ir/irText/firProblems/kt43342.kt b/compiler/testData/ir/irText/firProblems/kt43342.kt index 74f2623e677..7b88d7dc009 100644 --- a/compiler/testData/ir/irText/firProblems/kt43342.kt +++ b/compiler/testData/ir/irText/firProblems/kt43342.kt @@ -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 diff --git a/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/JsExportDeclarationChecker.kt b/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/JsExportDeclarationChecker.kt index 6d7d5caf8ac..73e25407b28 100644 --- a/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/JsExportDeclarationChecker.kt +++ b/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/JsExportDeclarationChecker.kt @@ -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 diff --git a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrJsES6TypeScriptExportTestGenerated.java b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrJsES6TypeScriptExportTestGenerated.java index 3d628a9eaee..f64279603e7 100644 --- a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrJsES6TypeScriptExportTestGenerated.java +++ b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrJsES6TypeScriptExportTestGenerated.java @@ -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") diff --git a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrJsTypeScriptExportTestGenerated.java b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrJsTypeScriptExportTestGenerated.java index 2f1ac395072..f8b8fc05dbf 100644 --- a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrJsTypeScriptExportTestGenerated.java +++ b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrJsTypeScriptExportTestGenerated.java @@ -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") diff --git a/js/js.translator/testData/typescript-export/collections-in-exported-file/collections.d.ts b/js/js.translator/testData/typescript-export/collections-in-exported-file/collections.d.ts new file mode 100644 index 00000000000..c1685a8fdaf --- /dev/null +++ b/js/js.translator/testData/typescript-export/collections-in-exported-file/collections.d.ts @@ -0,0 +1,53 @@ +declare namespace JS_TESTS { + type Nullable = T | null | undefined + namespace kotlin.collections { + interface KtList /* extends kotlin.collections.Collection */ { + asJsReadonlyArrayView(): ReadonlyArray; + readonly __doNotUseOrImplementIt: { + readonly "kotlin.collections.KtList": unique symbol; + }; + } + interface KtMap { + asJsReadonlyMapView(): ReadonlyMap; + readonly __doNotUseOrImplementIt: { + readonly "kotlin.collections.KtMap": unique symbol; + }; + } + interface KtMutableList extends kotlin.collections.KtList/*, kotlin.collections.MutableCollection */ { + asJsArrayView(): Array; + readonly __doNotUseOrImplementIt: { + readonly "kotlin.collections.KtMutableList": unique symbol; + } & kotlin.collections.KtList["__doNotUseOrImplementIt"]; + } + interface KtSet /* extends kotlin.collections.Collection */ { + asJsReadonlySetView(): ReadonlySet; + readonly __doNotUseOrImplementIt: { + readonly "kotlin.collections.KtSet": unique symbol; + }; + } + interface KtMutableSet extends kotlin.collections.KtSet/*, kotlin.collections.MutableCollection */ { + asJsSetView(): Set; + readonly __doNotUseOrImplementIt: { + readonly "kotlin.collections.KtMutableSet": unique symbol; + } & kotlin.collections.KtSet["__doNotUseOrImplementIt"]; + } + interface KtMutableMap extends kotlin.collections.KtMap { + asJsMapView(): Map; + readonly __doNotUseOrImplementIt: { + readonly "kotlin.collections.KtMutableMap": unique symbol; + } & kotlin.collections.KtMap["__doNotUseOrImplementIt"]; + } + } + function provideList(): kotlin.collections.KtList; + function provideMutableList(): kotlin.collections.KtMutableList; + function provideSet(): kotlin.collections.KtSet; + function provideMutableSet(): kotlin.collections.KtMutableSet; + function provideMap(): kotlin.collections.KtMap; + function provideMutableMap(): kotlin.collections.KtMutableMap; + function consumeList(list: kotlin.collections.KtList): boolean; + function consumeMutableList(list: kotlin.collections.KtMutableList): boolean; + function consumeSet(list: kotlin.collections.KtSet): boolean; + function consumeMutableSet(list: kotlin.collections.KtMutableSet): boolean; + function consumeMap(map: kotlin.collections.KtMap): boolean; + function consumeMutableMap(map: kotlin.collections.KtMutableMap): boolean; +} diff --git a/js/js.translator/testData/typescript-export/collections-in-exported-file/collections.kt b/js/js.translator/testData/typescript-export/collections-in-exported-file/collections.kt new file mode 100644 index 00000000000..5b0ecbf68de --- /dev/null +++ b/js/js.translator/testData/typescript-export/collections-in-exported-file/collections.kt @@ -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 = listOf(1, 2, 3) + + +fun provideMutableList(): MutableList = mutableListOf(4, 5, 6) + + +fun provideSet(): Set = setOf(1, 2, 3) + + +fun provideMutableSet(): MutableSet = mutableSetOf(4, 5, 6) + + +fun provideMap(): Map = mapOf("a" to 1, "b" to 2, "c" to 3) + + +fun provideMutableMap(): MutableMap = mutableMapOf("d" to 4, "e" to 5, "f" to 6) + + +fun consumeList(list: List) = list.toString() == "[1, 2, 3]" + + +fun consumeMutableList(list: MutableList): Boolean { + list.add(7) + return list.toString() == "[4, 5, 6, 7]" +} + + +fun consumeSet(list: Set) = list.toString() == "[1, 2, 3]" + + +fun consumeMutableSet(list: MutableSet): Boolean { + list.add(7) + return list.toString() == "[4, 5, 6, 7]" +} + + +fun consumeMap(map: Map) = map.toString() == "{a=1, b=2, c=3}" + + +fun consumeMutableMap(map: MutableMap): Boolean { + map["g"] = 7 + return map.toString() == "{d=4, e=5, f=6, g=7}" +} diff --git a/js/js.translator/testData/typescript-export/collections-in-exported-file/collections__main.ts b/js/js.translator/testData/typescript-export/collections-in-exported-file/collections__main.ts new file mode 100644 index 00000000000..6a0c47ad706 --- /dev/null +++ b/js/js.translator/testData/typescript-export/collections-in-exported-file/collections__main.ts @@ -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)[1] = 4 }, "Immutable list readonly array view have ability to mutate the list by direct set") + assertThrow(() => { (listReadonlyArrayView as Array).push(4) }, "Immutable list readonly array view have ability to mutate the list by 'push'") + assertThrow(() => { (listReadonlyArrayView as Array).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)[1] = 4 }, "Mutable list readonly array view have ability to mutate the list by direct set") + assertThrow(() => { (mutableListReadonlyArrayView as Array).push(4) }, "Mutable list readonly array view have ability to mutate the list by 'push'") + assertThrow(() => { (mutableListReadonlyArrayView as Array).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).add(4) }, "Set readonly view have ability to mutate the set by 'add'") + assertThrow(() => { (setReadonlyView as Set).delete(4) }, "Set readonly view have ability to mutate the set by 'delete'") + assertThrow(() => { (setReadonlyView as Set).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).add(4) }, "Mutable set readonly view have ability to mutate the set by 'add'") + assertThrow(() => { (mutableSetReadonlyView as Set).delete(4) }, "Mutable set readonly view have ability to mutate the set by 'delete'") + assertThrow(() => { (mutableSetReadonlyView as Set).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).set("d", 4) }, "Map readonly view have ability to mutate the map by 'set'") + assertThrow(() => { (mapReadonlyView as Map).delete("a") }, "Map readonly view have ability to mutate the map by 'delete'") + assertThrow(() => { (mapReadonlyView as Map).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).set("d", 4) }, "Mutable map readonly view have ability to mutate the map by 'set'") + assertThrow(() => { (mutableMapReadonlyView as Map).delete("a") }, "Mutable map readonly view have ability to mutate the map by 'delete'") + assertThrow(() => { (mutableMapReadonlyView as Map).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 | ReadonlyMap): 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 +} \ No newline at end of file diff --git a/js/js.translator/testData/typescript-export/collections-in-exported-file/tsconfig.json b/js/js.translator/testData/typescript-export/collections-in-exported-file/tsconfig.json new file mode 100644 index 00000000000..17612c56c9d --- /dev/null +++ b/js/js.translator/testData/typescript-export/collections-in-exported-file/tsconfig.json @@ -0,0 +1,4 @@ +{ + "include": [ "./*" ], + "extends": "../common.tsconfig.json" +} \ No newline at end of file diff --git a/js/js.translator/testData/typescript-export/collections/collections.d.ts b/js/js.translator/testData/typescript-export/collections/collections.d.ts new file mode 100644 index 00000000000..c1685a8fdaf --- /dev/null +++ b/js/js.translator/testData/typescript-export/collections/collections.d.ts @@ -0,0 +1,53 @@ +declare namespace JS_TESTS { + type Nullable = T | null | undefined + namespace kotlin.collections { + interface KtList /* extends kotlin.collections.Collection */ { + asJsReadonlyArrayView(): ReadonlyArray; + readonly __doNotUseOrImplementIt: { + readonly "kotlin.collections.KtList": unique symbol; + }; + } + interface KtMap { + asJsReadonlyMapView(): ReadonlyMap; + readonly __doNotUseOrImplementIt: { + readonly "kotlin.collections.KtMap": unique symbol; + }; + } + interface KtMutableList extends kotlin.collections.KtList/*, kotlin.collections.MutableCollection */ { + asJsArrayView(): Array; + readonly __doNotUseOrImplementIt: { + readonly "kotlin.collections.KtMutableList": unique symbol; + } & kotlin.collections.KtList["__doNotUseOrImplementIt"]; + } + interface KtSet /* extends kotlin.collections.Collection */ { + asJsReadonlySetView(): ReadonlySet; + readonly __doNotUseOrImplementIt: { + readonly "kotlin.collections.KtSet": unique symbol; + }; + } + interface KtMutableSet extends kotlin.collections.KtSet/*, kotlin.collections.MutableCollection */ { + asJsSetView(): Set; + readonly __doNotUseOrImplementIt: { + readonly "kotlin.collections.KtMutableSet": unique symbol; + } & kotlin.collections.KtSet["__doNotUseOrImplementIt"]; + } + interface KtMutableMap extends kotlin.collections.KtMap { + asJsMapView(): Map; + readonly __doNotUseOrImplementIt: { + readonly "kotlin.collections.KtMutableMap": unique symbol; + } & kotlin.collections.KtMap["__doNotUseOrImplementIt"]; + } + } + function provideList(): kotlin.collections.KtList; + function provideMutableList(): kotlin.collections.KtMutableList; + function provideSet(): kotlin.collections.KtSet; + function provideMutableSet(): kotlin.collections.KtMutableSet; + function provideMap(): kotlin.collections.KtMap; + function provideMutableMap(): kotlin.collections.KtMutableMap; + function consumeList(list: kotlin.collections.KtList): boolean; + function consumeMutableList(list: kotlin.collections.KtMutableList): boolean; + function consumeSet(list: kotlin.collections.KtSet): boolean; + function consumeMutableSet(list: kotlin.collections.KtMutableSet): boolean; + function consumeMap(map: kotlin.collections.KtMap): boolean; + function consumeMutableMap(map: kotlin.collections.KtMutableMap): boolean; +} diff --git a/js/js.translator/testData/typescript-export/collections/collections.kt b/js/js.translator/testData/typescript-export/collections/collections.kt new file mode 100644 index 00000000000..f8c0a57b31f --- /dev/null +++ b/js/js.translator/testData/typescript-export/collections/collections.kt @@ -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 = listOf(1, 2, 3) + +@JsExport +fun provideMutableList(): MutableList = mutableListOf(4, 5, 6) + +@JsExport +fun provideSet(): Set = setOf(1, 2, 3) + +@JsExport +fun provideMutableSet(): MutableSet = mutableSetOf(4, 5, 6) + +@JsExport +fun provideMap(): Map = mapOf("a" to 1, "b" to 2, "c" to 3) + +@JsExport +fun provideMutableMap(): MutableMap = mutableMapOf("d" to 4, "e" to 5, "f" to 6) + +@JsExport +fun consumeList(list: List) = list.toString() == "[1, 2, 3]" + +@JsExport +fun consumeMutableList(list: MutableList): Boolean { + list.add(7) + return list.toString() == "[4, 5, 6, 7]" +} + +@JsExport +fun consumeSet(list: Set) = list.toString() == "[1, 2, 3]" + +@JsExport +fun consumeMutableSet(list: MutableSet): Boolean { + list.add(7) + return list.toString() == "[4, 5, 6, 7]" +} + +@JsExport +fun consumeMap(map: Map) = map.toString() == "{a=1, b=2, c=3}" + +@JsExport +fun consumeMutableMap(map: MutableMap): Boolean { + map["g"] = 7 + return map.toString() == "{d=4, e=5, f=6, g=7}" +} diff --git a/js/js.translator/testData/typescript-export/collections/collections__main.ts b/js/js.translator/testData/typescript-export/collections/collections__main.ts new file mode 100644 index 00000000000..6a0c47ad706 --- /dev/null +++ b/js/js.translator/testData/typescript-export/collections/collections__main.ts @@ -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)[1] = 4 }, "Immutable list readonly array view have ability to mutate the list by direct set") + assertThrow(() => { (listReadonlyArrayView as Array).push(4) }, "Immutable list readonly array view have ability to mutate the list by 'push'") + assertThrow(() => { (listReadonlyArrayView as Array).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)[1] = 4 }, "Mutable list readonly array view have ability to mutate the list by direct set") + assertThrow(() => { (mutableListReadonlyArrayView as Array).push(4) }, "Mutable list readonly array view have ability to mutate the list by 'push'") + assertThrow(() => { (mutableListReadonlyArrayView as Array).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).add(4) }, "Set readonly view have ability to mutate the set by 'add'") + assertThrow(() => { (setReadonlyView as Set).delete(4) }, "Set readonly view have ability to mutate the set by 'delete'") + assertThrow(() => { (setReadonlyView as Set).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).add(4) }, "Mutable set readonly view have ability to mutate the set by 'add'") + assertThrow(() => { (mutableSetReadonlyView as Set).delete(4) }, "Mutable set readonly view have ability to mutate the set by 'delete'") + assertThrow(() => { (mutableSetReadonlyView as Set).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).set("d", 4) }, "Map readonly view have ability to mutate the map by 'set'") + assertThrow(() => { (mapReadonlyView as Map).delete("a") }, "Map readonly view have ability to mutate the map by 'delete'") + assertThrow(() => { (mapReadonlyView as Map).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).set("d", 4) }, "Mutable map readonly view have ability to mutate the map by 'set'") + assertThrow(() => { (mutableMapReadonlyView as Map).delete("a") }, "Mutable map readonly view have ability to mutate the map by 'delete'") + assertThrow(() => { (mutableMapReadonlyView as Map).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 | ReadonlyMap): 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 +} \ No newline at end of file diff --git a/js/js.translator/testData/typescript-export/collections/tsconfig.json b/js/js.translator/testData/typescript-export/collections/tsconfig.json new file mode 100644 index 00000000000..17612c56c9d --- /dev/null +++ b/js/js.translator/testData/typescript-export/collections/tsconfig.json @@ -0,0 +1,4 @@ +{ + "include": [ "./*" ], + "extends": "../common.tsconfig.json" +} \ No newline at end of file diff --git a/js/js.translator/testData/typescript-export/common.tsconfig.json b/js/js.translator/testData/typescript-export/common.tsconfig.json index 7a6773bd2f4..84be71503be 100644 --- a/js/js.translator/testData/typescript-export/common.tsconfig.json +++ b/js/js.translator/testData/typescript-export/common.tsconfig.json @@ -2,6 +2,7 @@ "compilerOptions": { "target": "es5", "strict": true, - "newLine": "lf" + "newLine": "lf", + "lib": ["es2015", "dom"] } } \ No newline at end of file diff --git a/js/js.translator/testData/typescript-export/functions-in-exported-file/functions.d.ts b/js/js.translator/testData/typescript-export/functions-in-exported-file/functions.d.ts index 4eb3bf71dff..4a4ed749a6a 100644 --- a/js/js.translator/testData/typescript-export/functions-in-exported-file/functions.d.ts +++ b/js/js.translator/testData/typescript-export/functions-in-exported-file/functions.d.ts @@ -1,5 +1,13 @@ declare namespace JS_TESTS { type Nullable = T | null | undefined + namespace kotlin.collections { + interface KtList /* extends kotlin.collections.Collection */ { + asJsReadonlyArrayView(): ReadonlyArray; + readonly __doNotUseOrImplementIt: { + readonly "kotlin.collections.KtList": unique symbol; + }; + } + } namespace foo { interface SomeExternalInterface { } @@ -18,8 +26,8 @@ declare namespace JS_TESTS { function genericWithMultipleConstraints */ & foo.SomeExternalInterface & Error>(x: T): T; function generic3(a: A, b: B, c: C, d: D): Nullable; function inlineFun(x: number, callback: (p0: number) => void): void; - function formatList(value: any/* kotlin.collections.List */): string; - function createList(): any/* kotlin.collections.List */; + function formatList(value: kotlin.collections.KtList): string; + function createList(): kotlin.collections.KtList; function defaultParametersAtTheBegining(a: string | undefined, b: string): string; function nonDefaultParameterInBetween(a: string | undefined, b: string, c?: string): string; } diff --git a/js/js.translator/testData/typescript-export/functions/functions.d.ts b/js/js.translator/testData/typescript-export/functions/functions.d.ts index 4eb3bf71dff..4a4ed749a6a 100644 --- a/js/js.translator/testData/typescript-export/functions/functions.d.ts +++ b/js/js.translator/testData/typescript-export/functions/functions.d.ts @@ -1,5 +1,13 @@ declare namespace JS_TESTS { type Nullable = T | null | undefined + namespace kotlin.collections { + interface KtList /* extends kotlin.collections.Collection */ { + asJsReadonlyArrayView(): ReadonlyArray; + readonly __doNotUseOrImplementIt: { + readonly "kotlin.collections.KtList": unique symbol; + }; + } + } namespace foo { interface SomeExternalInterface { } @@ -18,8 +26,8 @@ declare namespace JS_TESTS { function genericWithMultipleConstraints */ & foo.SomeExternalInterface & Error>(x: T): T; function generic3(a: A, b: B, c: C, d: D): Nullable; function inlineFun(x: number, callback: (p0: number) => void): void; - function formatList(value: any/* kotlin.collections.List */): string; - function createList(): any/* kotlin.collections.List */; + function formatList(value: kotlin.collections.KtList): string; + function createList(): kotlin.collections.KtList; function defaultParametersAtTheBegining(a: string | undefined, b: string): string; function nonDefaultParameterInBetween(a: string | undefined, b: string, c?: string): string; } diff --git a/libraries/stdlib/api/js/kotlin.collections.kt b/libraries/stdlib/api/js/kotlin.collections.kt index 1308e178937..fa19b51adbc 100644 --- a/libraries/stdlib/api/js/kotlin.collections.kt +++ b/libraries/stdlib/api/js/kotlin.collections.kt @@ -10111,6 +10111,11 @@ public open class ArrayList : kotlin.collections.AbstractMutableList, kotl public open override fun addAll(elements: kotlin.collections.Collection): kotlin.Boolean + @kotlin.js.ExperimentalJsExport + @kotlin.js.ExperimentalJsCollectionsApi + @kotlin.SinceKotlin(version = "1.9") + public open override fun asJsArrayView(): kotlin.js.collections.JsArray + public open override fun clear(): kotlin.Unit public final fun ensureCapacity(minCapacity: kotlin.Int): kotlin.Unit @@ -10310,6 +10315,11 @@ public open class LinkedHashSet : kotlin.collections.HashSet, kotlin.colle public interface List : kotlin.collections.Collection { 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 + public abstract override operator fun contains(element: E): kotlin.Boolean public abstract override fun containsAll(elements: kotlin.collections.Collection): kotlin.Boolean @@ -10362,6 +10372,11 @@ public interface Map { public abstract val values: kotlin.collections.Collection { get; } + @kotlin.js.ExperimentalJsExport + @kotlin.js.ExperimentalJsCollectionsApi + @kotlin.SinceKotlin(version = "1.9") + public open fun asJsReadonlyMapView(): kotlin.js.collections.JsReadonlyMap + public abstract fun containsKey(key: K): kotlin.Boolean public abstract fun containsValue(value: V): kotlin.Boolean @@ -10410,6 +10425,11 @@ public interface MutableList : kotlin.collections.List, kotlin.collections public abstract override fun addAll(elements: kotlin.collections.Collection): kotlin.Boolean + @kotlin.js.ExperimentalJsExport + @kotlin.js.ExperimentalJsCollectionsApi + @kotlin.SinceKotlin(version = "1.9") + public open fun asJsArrayView(): kotlin.js.collections.JsArray + public abstract override fun clear(): kotlin.Unit public abstract override fun listIterator(): kotlin.collections.MutableListIterator @@ -10448,6 +10468,11 @@ public interface MutableMap : kotlin.collections.Map { public abstract override val values: kotlin.collections.MutableCollection { get; } + @kotlin.js.ExperimentalJsExport + @kotlin.js.ExperimentalJsCollectionsApi + @kotlin.SinceKotlin(version = "1.9") + public open fun asJsMapView(): kotlin.js.collections.JsMap + public abstract fun clear(): kotlin.Unit public abstract fun put(key: K, value: V): V? @@ -10466,6 +10491,11 @@ public interface MutableSet : kotlin.collections.Set, kotlin.collections.M public abstract override fun addAll(elements: kotlin.collections.Collection): kotlin.Boolean + @kotlin.js.ExperimentalJsExport + @kotlin.js.ExperimentalJsCollectionsApi + @kotlin.SinceKotlin(version = "1.9") + public open fun asJsSetView(): kotlin.js.collections.JsSet + public abstract override fun clear(): kotlin.Unit public abstract override operator fun iterator(): kotlin.collections.MutableIterator @@ -10483,6 +10513,11 @@ public interface RandomAccess { public interface Set : kotlin.collections.Collection { 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 + public abstract override operator fun contains(element: E): kotlin.Boolean public abstract override fun containsAll(elements: kotlin.collections.Collection): kotlin.Boolean @@ -10498,4 +10533,4 @@ public abstract class ShortIterator : kotlin.collections.Iterator public final override operator fun next(): kotlin.Short public abstract fun nextShort(): kotlin.Short -} \ No newline at end of file +} diff --git a/libraries/stdlib/api/js/kotlin.js.collections.kt b/libraries/stdlib/api/js/kotlin.js.collections.kt new file mode 100644 index 00000000000..052b9cf1659 --- /dev/null +++ b/libraries/stdlib/api/js/kotlin.js.collections.kt @@ -0,0 +1,38 @@ +@kotlin.js.JsName(name = "Array") +@kotlin.SinceKotlin(version = "1.9") +@kotlin.js.ExperimentalJsCollectionsApi +public open external class JsArray : kotlin.js.collections.JsReadonlyArray { + public constructor JsArray() +} + +@kotlin.js.JsName(name = "Map") +@kotlin.SinceKotlin(version = "1.9") +@kotlin.js.ExperimentalJsCollectionsApi +public open external class JsMap : kotlin.js.collections.JsReadonlyMap { + public constructor JsMap() +} + +@kotlin.js.JsName(name = "ReadonlyArray") +@kotlin.SinceKotlin(version = "1.9") +@kotlin.js.ExperimentalJsCollectionsApi +public external interface JsReadonlyArray { +} + +@kotlin.js.JsName(name = "ReadonlyMap") +@kotlin.SinceKotlin(version = "1.9") +@kotlin.js.ExperimentalJsCollectionsApi +public external interface JsReadonlyMap { +} + +@kotlin.js.JsName(name = "ReadonlySet") +@kotlin.SinceKotlin(version = "1.9") +@kotlin.js.ExperimentalJsCollectionsApi +public external interface JsReadonlySet { +} + +@kotlin.js.JsName(name = "Set") +@kotlin.SinceKotlin(version = "1.9") +@kotlin.js.ExperimentalJsCollectionsApi +public open external class JsSet : kotlin.js.collections.JsReadonlySet { + public constructor JsSet() +} diff --git a/libraries/stdlib/api/js/kotlin.js.kt b/libraries/stdlib/api/js/kotlin.js.kt index 876071cb0b9..a683d6ea7a5 100644 --- a/libraries/stdlib/api/js/kotlin.js.kt +++ b/libraries/stdlib/api/js/kotlin.js.kt @@ -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) diff --git a/libraries/stdlib/common/src/kotlin/JsAnnotationsH.kt b/libraries/stdlib/common/src/kotlin/JsAnnotationsH.kt index d383712ac77..2b9934c0dd1 100644 --- a/libraries/stdlib/common/src/kotlin/JsAnnotationsH.kt +++ b/libraries/stdlib/common/src/kotlin/JsAnnotationsH.kt @@ -130,4 +130,19 @@ public expect annotation class JsExport() { ) @MustBeDocumented @SinceKotlin("1.9") -public annotation class ExperimentalJsReflectionCreateInstance \ No newline at end of file +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 diff --git a/libraries/stdlib/js/builtins/Collections.kt b/libraries/stdlib/js/builtins/Collections.kt index becf0aee8a6..e6bfb341851 100644 --- a/libraries/stdlib/js/builtins/Collections.kt +++ b/libraries/stdlib/js/builtins/Collections.kt @@ -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 : Collection { // Query Operations override val size: Int + override fun isEmpty(): Boolean + override fun contains(element: @UnsafeVariance E): Boolean + override fun iterator(): Iterator // Bulk Operations @@ -191,6 +196,15 @@ public interface List : Collection { * Structural changes in the base list make the behavior of the view undefined. */ public fun subList(fromIndex: Int, toIndex: Int): List + + /** + * 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 = createJsReadonlyArrayViewFrom(this) } /** @@ -226,7 +240,9 @@ public interface MutableList : List, MutableCollection { public fun addAll(index: Int, elements: Collection): Boolean override fun removeAll(elements: Collection): Boolean + override fun retainAll(elements: Collection): Boolean + override fun clear(): Unit // Positional Access Operations @@ -256,6 +272,15 @@ public interface MutableList : List, MutableCollection { // View override fun subList(fromIndex: Int, toIndex: Int): MutableList + + /** + * 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 = createJsArrayViewFrom(this) } /** @@ -268,12 +293,24 @@ public interface Set : Collection { // Query Operations override val size: Int + override fun isEmpty(): Boolean + override fun contains(element: @UnsafeVariance E): Boolean + override fun iterator(): Iterator // 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 = createJsReadonlySetViewFrom(this) } /** @@ -299,9 +336,21 @@ public interface MutableSet : Set, MutableCollection { // Bulk Modification Operations override fun addAll(elements: Collection): Boolean + override fun removeAll(elements: Collection): Boolean + override fun retainAll(elements: Collection): 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 = createJsSetViewFrom(this) } /** @@ -370,6 +419,15 @@ public interface Map { */ 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 = createJsReadonlyMapViewFrom(this) } /** @@ -432,4 +490,13 @@ public interface MutableMap : Map { */ 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 = createJsMapViewFrom(this) } diff --git a/libraries/stdlib/js/runtime/collectionsInterop.kt b/libraries/stdlib/js/runtime/collectionsInterop.kt new file mode 100644 index 00000000000..88c6acd80ae --- /dev/null +++ b/libraries/stdlib/js/runtime/collectionsInterop.kt @@ -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 : JsArray() + +private fun UNSUPPORTED_OPERATION() { + throw UnsupportedOperationException() +} + +internal fun createJsReadonlyArrayViewFrom(list: List): JsReadonlyArray = + createJsArrayViewWith( + listSize = { list.size }, + listGet = { i -> list[i] }, + listSet = ::UNSUPPORTED_OPERATION.asDynamic(), + listDecreaseSize = ::UNSUPPORTED_OPERATION.asDynamic(), + listIncreaseSize = ::UNSUPPORTED_OPERATION.asDynamic() + ) + +internal fun createJsArrayViewFrom(list: MutableList): JsArray = + 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 createJsArrayViewWith( + listSize: () -> Int, + listGet: (Int) -> E, + listSet: (Int, E) -> Unit, + listDecreaseSize: (Int) -> Unit, + listIncreaseSize: (Int) -> Unit, +): dynamic { + val arrayView = objectCreate>() + + 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 : JsSet() + +internal fun createJsReadonlySetViewFrom(set: Set): JsReadonlySet = + createJsSetViewWith( + 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 createJsSetViewFrom(set: MutableSet): JsSet = + createJsSetViewWith( + 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 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>().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 : JsMap() + +internal fun createJsReadonlyMapViewFrom(map: Map): JsReadonlyMap = + createJsMapViewWith( + 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 createJsMapViewFrom(map: MutableMap): JsMap = + createJsMapViewWith( + 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 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>().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 createJsIteratorFrom(iterator: Iterator, 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() + } +} \ No newline at end of file diff --git a/libraries/stdlib/js/runtime/coreRuntime.kt b/libraries/stdlib/js/runtime/coreRuntime.kt index 84a40d859d4..08d7146f45e 100644 --- a/libraries/stdlib/js/runtime/coreRuntime.kt +++ b/libraries/stdlib/js/runtime/coreRuntime.kt @@ -208,7 +208,7 @@ internal fun protoOf(constructor: Any) = js("constructor.prototype") @Suppress("UNUSED_PARAMETER") -internal fun objectCreate(proto: T?) = +internal fun objectCreate(proto: T? = null): T = js("Object.create(proto)") @Suppress("UNUSED_PARAMETER") diff --git a/libraries/stdlib/js/runtime/kotlinJsHacks.kt b/libraries/stdlib/js/runtime/kotlinJsHacks.kt index c87d298dc78..9cdd101f853 100644 --- a/libraries/stdlib/js/runtime/kotlinJsHacks.kt +++ b/libraries/stdlib/js/runtime/kotlinJsHacks.kt @@ -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) diff --git a/libraries/stdlib/js/src/kotlin/collections/ArrayList.kt b/libraries/stdlib/js/src/kotlin/collections/ArrayList.kt index db4b31d191a..7d588c769b1 100644 --- a/libraries/stdlib/js/src/kotlin/collections/ArrayList.kt +++ b/libraries/stdlib/js/src/kotlin/collections/ArrayList.kt @@ -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 internal constructor(private var array: Ar return js("[]").slice.call(array) } + @ExperimentalJsExport + @ExperimentalJsCollectionsApi + @SinceKotlin("1.9") + override fun asJsArrayView(): JsArray = array.unsafeCast>() internal override fun checkIsMutable() { if (isReadOnly) throw UnsupportedOperationException() diff --git a/libraries/stdlib/js/src/kotlin/js.collections.kt b/libraries/stdlib/js/src/kotlin/js.collections.kt new file mode 100644 index 00000000000..22eea92b880 --- /dev/null +++ b/libraries/stdlib/js/src/kotlin/js.collections.kt @@ -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 + +/** + * 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 : JsReadonlyArray + +/** + * 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 + +/** + * 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 : JsReadonlySet + +/** + * 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 + +/** + * 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 : JsReadonlyMap \ No newline at end of file diff --git a/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib-runtime-merged.txt b/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib-runtime-merged.txt index 6421e1703c0..f4f32e705a3 100644 --- a/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib-runtime-merged.txt +++ b/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib-runtime-merged.txt @@ -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 { }