From f22de869541cc8af1500b57aff8b63ed8185eaec Mon Sep 17 00:00:00 2001 From: Ilya Goncharov Date: Wed, 3 Nov 2021 09:56:22 +0000 Subject: [PATCH] [JS IR] Possibility to use export for nested inside object [JS IR] Fix test data after master rebase [JS IR] Use stdlib's listOfNotNull [JS IR] Possibility to use export for nested inside object - Add tests for such cases Merge-request: KT-MR-4883 ^KT-49363 fixed --- .../kotlin/ir/backend/js/codegen/IrToJs.kt | 6 ++- .../ir/backend/js/export/ExportModel.kt | 1 + .../backend/js/export/ExportModelGenerator.kt | 27 +++++----- .../js/export/ExportModelToJsStatements.kt | 50 +++++++++++++------ .../testData/box/export/exportNestedClass.kt | 20 +++++++- .../testData/box/export/exportNestedObject.kt | 17 +++++++ 6 files changed, 91 insertions(+), 30 deletions(-) diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/codegen/IrToJs.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/codegen/IrToJs.kt index 00042e92813..d883cb38df4 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/codegen/IrToJs.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/codegen/IrToJs.kt @@ -241,7 +241,11 @@ class IrToJs( declareNewNamespace = { globalNames.declareFreshName(it, it) } ) exportedDeclarations.forEach { - statements += exporter.generateDeclarationExport(it, null) + statements += exporter.generateDeclarationExport( + it, + null, + esModules = true + ) } return GeneratedUnit(statements, exportedDeclarations) diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModel.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModel.kt index 617ab447c56..de6a20ed346 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModel.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModel.kt @@ -55,6 +55,7 @@ class ExportedProperty( val isProtected: Boolean, val irGetter: IrFunction?, val irSetter: IrFunction?, + val exportedObject: ExportedClass? = null, ) : ExportedDeclaration() 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 a1e8161d8c7..6a660fcb4c1 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 @@ -337,6 +337,18 @@ class ExportModelGenerator( val name = klass.getExportedIdentifier() + val exportedClass = ExportedClass( + name = name, + isInterface = klass.isInterface, + isAbstract = klass.modality == Modality.ABSTRACT, + superClass = superType, + superInterfaces = superInterfaces, + typeParameters = typeParameters, + members = members, + nestedClasses = nestedClasses, + ir = klass + ) + if (klass.kind == ClassKind.OBJECT) { var t: ExportedType = ExportedType.InlineInterfaceType(members + nestedClasses) if (superType != null) @@ -355,21 +367,12 @@ class ExportModelGenerator( isAbstract = false, isProtected = klass.visibility == DescriptorVisibilities.PROTECTED, irGetter = context.mapping.objectToGetInstanceFunction[klass]!!, - irSetter = null + irSetter = null, + exportedObject = exportedClass, ) } - return ExportedClass( - name = name, - isInterface = klass.isInterface, - isAbstract = klass.modality == Modality.ABSTRACT, - superClass = superType, - superInterfaces = superInterfaces, - typeParameters = typeParameters, - members = members, - nestedClasses = nestedClasses, - ir = klass - ) + return exportedClass } private fun exportAsEnumMember( diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModelToJsStatements.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModelToJsStatements.kt index c742cefde71..8102d85f6e3 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModelToJsStatements.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModelToJsStatements.kt @@ -11,6 +11,7 @@ import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.jsAssignment import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.prototypeOf import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.jsElementAccess import org.jetbrains.kotlin.ir.backend.js.utils.IrNamer +import org.jetbrains.kotlin.ir.backend.js.utils.Namer import org.jetbrains.kotlin.ir.backend.js.utils.emptyScope import org.jetbrains.kotlin.ir.backend.js.utils.getJsNameOrKotlinName import org.jetbrains.kotlin.ir.util.companionObject @@ -24,10 +25,14 @@ class ExportModelToJsStatements( private val namespaceToRefMap = mutableMapOf() fun generateModuleExport(module: ExportedModule, internalModuleName: JsName): List { - return module.declarations.flatMap { generateDeclarationExport(it, JsNameRef(internalModuleName)) } + return module.declarations.flatMap { generateDeclarationExport(it, JsNameRef(internalModuleName), esModules = false) } } - fun generateDeclarationExport(declaration: ExportedDeclaration, namespace: JsExpression?): List { + fun generateDeclarationExport( + declaration: ExportedDeclaration, + namespace: JsExpression?, + esModules: Boolean + ): List { return when (declaration) { is ExportedNamespace -> { require(namespace != null) { "Only namespaced namespaces are allowed" } @@ -57,20 +62,22 @@ class ExportModelToJsStatements( currentRef = newNameSpaceRef currentNamespace = newNamespace } - statements + declaration.declarations.flatMap { generateDeclarationExport(it, currentRef) } + statements + declaration.declarations.flatMap { generateDeclarationExport(it, currentRef, esModules) } } is ExportedFunction -> { val name = namer.getNameForStaticDeclaration(declaration.ir) - if (namespace == null) { + if (esModules) { listOf(JsExport(name, alias = JsName(declaration.name, false))) } else { - listOf( - jsAssignment( - jsElementAccess(declaration.name, namespace), - JsNameRef(name) - ).makeStmt() - ) + if (namespace != null) { + listOf( + jsAssignment( + jsElementAccess(declaration.name, namespace), + JsNameRef(name) + ).makeStmt() + ) + } else emptyList() } } @@ -79,22 +86,33 @@ class ExportModelToJsStatements( is ExportedProperty -> { require(namespace != null) { "Only namespaced properties are allowed" } + val underlying: List = declaration.exportedObject?.let { + generateDeclarationExport(it, null, esModules) + } ?: emptyList() val getter = declaration.irGetter?.let { JsNameRef(namer.getNameForStaticDeclaration(it)) } val setter = declaration.irSetter?.let { JsNameRef(namer.getNameForStaticDeclaration(it)) } - listOf(defineProperty(namespace, declaration.name, getter, setter).makeStmt()) + listOf(defineProperty(namespace, declaration.name, getter, setter).makeStmt()) + underlying } is ErrorDeclaration -> emptyList() is ExportedClass -> { if (declaration.isInterface) return emptyList() - val newNameSpace = jsElementAccess(declaration.name, namespace) + val newNameSpace = if (namespace != null) + jsElementAccess(declaration.name, namespace) + else + JsNameRef(Namer.PROTOTYPE_NAME, declaration.name) val name = namer.getNameForStaticDeclaration(declaration.ir) val klassExport = - if (namespace == null) { + if (esModules) { JsExport(name, alias = JsName(declaration.name, false)) } else { - jsAssignment(newNameSpace, JsNameRef(name)).makeStmt() + if (namespace != null) { + jsAssignment( + newNameSpace, + JsNameRef(name) + ).makeStmt() + } else null } // These are only used when exporting secondary constructors annotated with @JsName @@ -112,9 +130,9 @@ class ExportModelToJsStatements( .map { it.generateInnerClassAssignment(declaration) } val staticsExport = (staticFunctions + staticProperties + declaration.nestedClasses) - .flatMap { generateDeclarationExport(it, newNameSpace) } + .flatMap { generateDeclarationExport(it, newNameSpace, esModules) } - listOf(klassExport) + staticsExport + innerClassesAssignments + listOfNotNull(klassExport) + staticsExport + innerClassesAssignments } } } diff --git a/js/js.translator/testData/box/export/exportNestedClass.kt b/js/js.translator/testData/box/export/exportNestedClass.kt index 99bff45a3f4..37ae1352465 100644 --- a/js/js.translator/testData/box/export/exportNestedClass.kt +++ b/js/js.translator/testData/box/export/exportNestedClass.kt @@ -24,7 +24,25 @@ class B { } } +@JsExport +object MyObject { + class A { + fun valueA() = "OK" + } + class B { + fun valueB() = "OK" + } + class C { + fun valueC() = "OK" + } +} + // FILE: test.js function box() { - return new this["export_nested_class"].B.Foo().bar("K"); + if (new this["export_nested_class"].B.Foo().bar("K") != "OK") return "fail 1"; + if (new this["export_nested_class"].MyObject.A().valueA() != "OK") return "fail 2"; + if (new this["export_nested_class"].MyObject.B().valueB() != "OK") return "fail 3"; + if (new this["export_nested_class"].MyObject.C().valueC() != "OK") return "fail 4"; + + return "OK" } \ No newline at end of file diff --git a/js/js.translator/testData/box/export/exportNestedObject.kt b/js/js.translator/testData/box/export/exportNestedObject.kt index 200dacb8146..e9a06104578 100644 --- a/js/js.translator/testData/box/export/exportNestedObject.kt +++ b/js/js.translator/testData/box/export/exportNestedObject.kt @@ -37,6 +37,19 @@ sealed class MyEnum(val name: String) { object C: MyEnum("C") } +@JsExport +object MyObject { + object A { + fun valueA() = "OK" + } + object B { + fun valueB() = "OK" + } + object C { + fun valueC() = "OK" + } +} + // FILE: test.js function box() { @@ -54,5 +67,9 @@ function box() { if (nestedObjectExport.MyEnum.B.name != 'B') return 'MyEnum.B failure'; if (nestedObjectExport.MyEnum.C.name != 'C') return 'MyEnum.C failure'; + if (nestedObjectExport.MyObject.A.valueA() != "OK") return 'MyObject.A failure'; + if (nestedObjectExport.MyObject.B.valueB() != "OK") return 'MyObject.B failure'; + if (nestedObjectExport.MyObject.C.valueC() != "OK") return 'MyObject.C failure'; + return 'OK'; }