[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
This commit is contained in:
Ilya Goncharov
2021-11-03 09:56:22 +00:00
committed by Space
parent 540419a5d7
commit f22de86954
6 changed files with 91 additions and 30 deletions
@@ -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)
@@ -55,6 +55,7 @@ class ExportedProperty(
val isProtected: Boolean,
val irGetter: IrFunction?,
val irSetter: IrFunction?,
val exportedObject: ExportedClass? = null,
) : ExportedDeclaration()
@@ -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(
@@ -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<String, JsNameRef>()
fun generateModuleExport(module: ExportedModule, internalModuleName: JsName): List<JsStatement> {
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<JsStatement> {
fun generateDeclarationExport(
declaration: ExportedDeclaration,
namespace: JsExpression?,
esModules: Boolean
): List<JsStatement> {
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<JsStatement> = 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
}
}
}
+19 -1
View File
@@ -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"
}
@@ -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';
}