[JS] Support secondary constructors in JsExport

This commit is contained in:
Svyatoslav Kuzmich
2020-05-20 18:00:03 +03:00
parent f128e5222a
commit 695d383ed1
14 changed files with 233 additions and 17 deletions
@@ -5,7 +5,9 @@
package org.jetbrains.kotlin.ir.backend.js.export
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.declarations.IrClass
import org.jetbrains.kotlin.ir.declarations.IrProperty
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
sealed class ExportedDeclaration
@@ -56,7 +58,7 @@ class ExportedClass(
val superInterfaces: List<ExportedType> = emptyList(),
val typeParameters: List<String>,
val members: List<ExportedDeclaration>,
val statics: List<ExportedDeclaration>,
val nestedClasses: List<ExportedClass>,
val ir: IrClass
) : ExportedDeclaration()
@@ -6,7 +6,6 @@
package org.jetbrains.kotlin.ir.backend.js.export
import org.jetbrains.kotlin.backend.common.ir.isExpect
import org.jetbrains.kotlin.backend.common.ir.isMethodOfAny
import org.jetbrains.kotlin.config.CommonConfigurationKeys
import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.descriptors.Modality
@@ -23,8 +22,6 @@ import org.jetbrains.kotlin.ir.symbols.IrClassifierSymbol
import org.jetbrains.kotlin.ir.symbols.IrTypeParameterSymbol
import org.jetbrains.kotlin.ir.types.*
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.FqNameUnsafe
import org.jetbrains.kotlin.utils.addIfNotNull
class ExportModelGenerator(val context: JsIrBackendContext) {
@@ -139,14 +136,14 @@ class ExportModelGenerator(val context: JsIrBackendContext) {
private fun exportClass(
klass: IrClass
): ExportedDeclaration? {
): ExportedClass? {
when (val exportability = classExportability(klass)) {
is Exportability.Prohibited -> return ErrorDeclaration(exportability.reason)
is Exportability.Prohibited -> return error(exportability.reason)
is Exportability.NotNeeded -> return null
}
val members = mutableListOf<ExportedDeclaration>()
val statics = mutableListOf<ExportedDeclaration>()
val nestedClasses = mutableListOf<ExportedClass>()
for (declaration in klass.declarations) {
val candidate = getExportCandidate(declaration) ?: continue
@@ -163,7 +160,7 @@ class ExportModelGenerator(val context: JsIrBackendContext) {
members.addIfNotNull(exportProperty(candidate))
is IrClass ->
statics.addIfNotNull(exportClass(candidate))
nestedClasses.addIfNotNull(exportClass(candidate))
is IrField -> {
assert(candidate.correspondingPropertySymbol != null) {
@@ -198,7 +195,7 @@ class ExportModelGenerator(val context: JsIrBackendContext) {
superInterfaces = superInterfaces,
typeParameters = typeParameters,
members = members,
statics = statics,
nestedClasses = nestedClasses,
ir = klass
)
}
@@ -413,4 +410,4 @@ val strictModeReservedWords = setOf(
"yield"
)
private val allReservedWords = reservedWords + strictModeReservedWords
private val allReservedWords = reservedWords + strictModeReservedWords
@@ -8,7 +8,8 @@ package org.jetbrains.kotlin.ir.backend.js.export
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.JsAstUtils
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.defineProperty
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.jsAssignment
import org.jetbrains.kotlin.ir.backend.js.utils.*
import org.jetbrains.kotlin.ir.backend.js.utils.NameTable
import org.jetbrains.kotlin.ir.backend.js.utils.NameTables
import org.jetbrains.kotlin.js.backend.ast.*
@@ -85,7 +86,12 @@ class ExportModelToJsStatements(
)
)
).makeStmt()
val staticsExport = declaration.statics.flatMap { generateDeclarationExport(it, newNameSpace) }
val staticFunctions = declaration.members.filter { it is ExportedFunction && it.isStatic }
val staticsExport = (staticFunctions + declaration.nestedClasses)
.flatMap { generateDeclarationExport(it, newNameSpace) }
listOf(klassExport) + staticsExport
}
}
@@ -75,7 +75,7 @@ fun ExportedDeclaration.toTypeScript(indent: String): String = indent + when (th
val modifiers = if (isAbstract && !isInterface) "abstract " else ""
val klassExport = "$modifiers$keyword $name$renderedTypeParameters$superClassClause$superInterfacesClause {\n$membersString$indent}"
val staticsExport = if (statics.isNotEmpty()) "\n" + ExportedNamespace(name, statics).toTypeScript(indent) else ""
val staticsExport = if (nestedClasses.isNotEmpty()) "\n" + ExportedNamespace(name, nestedClasses).toTypeScript(indent) else ""
klassExport + staticsExport
}
}
@@ -17,6 +17,7 @@ import org.jetbrains.kotlin.ir.IrStatement
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext
import org.jetbrains.kotlin.ir.backend.js.ir.JsIrBuilder
import org.jetbrains.kotlin.ir.backend.js.utils.getJsName
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.expressions.impl.IrBlockBodyImpl
@@ -176,9 +177,10 @@ private fun buildFactoryDeclaration(constructor: IrConstructor, irClass: IrClass
Modality.FINAL,
constructor.isInline,
constructor.isExternal
).also {
it.copyTypeParametersFrom(constructor.parentAsClass)
it.valueParameters += constructor.valueParameters.map { p -> p.copyTo(it) }
).also { factory ->
factory.copyTypeParametersFrom(constructor.parentAsClass)
factory.valueParameters += constructor.valueParameters.map { p -> p.copyTo(factory) }
factory.annotations = constructor.annotations
}
}
@@ -29,6 +29,24 @@ public class IrJsTypeScriptExportTestGenerated extends AbstractIrJsTypeScriptExp
KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("js/js.translator/testData/typescript-export"), Pattern.compile("^([^_](.+))\\.kt$"), null, TargetBackend.JS_IR, true);
}
@TestMetadata("js/js.translator/testData/typescript-export/constructors")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class Constructors extends AbstractIrJsTypeScriptExportTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest0(this::doTest, TargetBackend.JS_IR, testDataFilePath);
}
public void testAllFilesPresentInConstructors() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("js/js.translator/testData/typescript-export/constructors"), Pattern.compile("^([^_](.+))\\.kt$"), null, TargetBackend.JS_IR, true);
}
@TestMetadata("constructors.kt")
public void testConstructors() throws Exception {
runTest("js/js.translator/testData/typescript-export/constructors/constructors.kt");
}
}
@TestMetadata("js/js.translator/testData/typescript-export/declarations")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
@@ -29,6 +29,19 @@ public class LegacyJsTypeScriptExportTestGenerated extends AbstractLegacyJsTypeS
KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("js/js.translator/testData/typescript-export"), Pattern.compile("^([^_](.+))\\.kt$"), null, TargetBackend.JS, true);
}
@TestMetadata("js/js.translator/testData/typescript-export/constructors")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class Constructors extends AbstractLegacyJsTypeScriptExportTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest0(this::doTest, TargetBackend.JS, testDataFilePath);
}
public void testAllFilesPresentInConstructors() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("js/js.translator/testData/typescript-export/constructors"), Pattern.compile("^([^_](.+))\\.kt$"), null, TargetBackend.JS, true);
}
}
@TestMetadata("js/js.translator/testData/typescript-export/declarations")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
@@ -1,5 +1,8 @@
// EXPECTED_REACHABLE_NODES: 1290
// TODO: Support JsExport on object declarations: KT-39117
// IGNORE_BACKEND: JS_IR
@JsExport
object A {
@JsName("js_method") fun f() = "method"
+3
View File
@@ -1,5 +1,8 @@
// EXPECTED_REACHABLE_NODES: 1291
// TODO: Support JsExport on object declarations: KT-39117
// IGNORE_BACKEND: JS_IR
@JsExport
object A {
@JsName("js_f") fun f(x: Int) = "f($x)"
@@ -0,0 +1,30 @@
declare namespace JS_TESTS {
type Nullable<T> = T | null | undefined
class ClassWithDefaultCtor {
constructor()
readonly x: string;
}
class ClassWithPrimaryCtor {
constructor(x: string)
readonly x: string;
}
class ClassWithSecondaryCtor {
readonly x: string;
static create(y: string): ClassWithSecondaryCtor
}
class ClassWithMultipleSecondaryCtors {
readonly x: string;
static createFromString(y: string): ClassWithMultipleSecondaryCtors
static createFromInts(y: number, z: number): ClassWithMultipleSecondaryCtors
}
class OpenClassWithMixedConstructors {
constructor(x: string)
readonly x: string;
static createFromStrings(y: string, z: string): OpenClassWithMixedConstructors
static createFromInts(y: number, z: number): OpenClassWithMixedConstructors
}
class DerivedClassWithSecondaryCtor extends OpenClassWithMixedConstructors {
static delegateToPrimary(y: string): DerivedClassWithSecondaryCtor
static delegateToCreateFromInts(y: number, z: number): DerivedClassWithSecondaryCtor
}
}
@@ -0,0 +1,56 @@
// TARGET_BACKEND: JS_IR
// CHECK_TYPESCRIPT_DECLARATIONS
// RUN_PLAIN_BOX_FUNCTION
// SKIP_MINIFICATION
// SKIP_NODE_JS
// TODO fix statics export in DCE-driven mode
// SKIP_DCE_DRIVEN
@file:JsExport
class ClassWithDefaultCtor {
val x = "ClassWithDefaultCtor::x"
}
class ClassWithPrimaryCtor(
val x: String
)
class ClassWithSecondaryCtor {
val x: String
@JsName("create")
constructor(y: String) {
x = y
}
}
class ClassWithMultipleSecondaryCtors {
val x: String
@JsName("createFromString")
constructor(y: String) {
x = "fromString:$y"
}
@JsName("createFromInts")
constructor(y: Int, z: Int) {
x = "fromInts:$y:$z"
}
}
open class OpenClassWithMixedConstructors(val x: String) {
@JsName("createFromStrings")
constructor(y: String, z: String) : this("fromStrings:$y:$z")
@JsName("createFromInts")
constructor(y: Int, z: Int) : this(y.toString(), z.toString())
}
class DerivedClassWithSecondaryCtor : OpenClassWithMixedConstructors {
@JsName("delegateToPrimary")
constructor(y: String) : super(y)
@JsName("delegateToCreateFromInts")
constructor(y: Int, z: Int) : super(y, z)
}
@@ -0,0 +1,35 @@
"use strict";
var ClassWithDefaultCtor = JS_TESTS.ClassWithDefaultCtor, ClassWithPrimaryCtor = JS_TESTS.ClassWithPrimaryCtor, ClassWithSecondaryCtor = JS_TESTS.ClassWithSecondaryCtor, ClassWithMultipleSecondaryCtors = JS_TESTS.ClassWithMultipleSecondaryCtors, DerivedClassWithSecondaryCtor = JS_TESTS.DerivedClassWithSecondaryCtor, OpenClassWithMixedConstructors = JS_TESTS.OpenClassWithMixedConstructors;
function box() {
var o1 = new ClassWithDefaultCtor();
if (o1.x !== "ClassWithDefaultCtor::x")
return "Fail: ClassWithDefaultCtor";
var o2 = new ClassWithPrimaryCtor("foo");
if (o2.x !== "foo")
return "Fail: ClassWithPrimaryCtor";
var o3 = ClassWithSecondaryCtor.create("foo2");
if (o3.x !== "foo2")
return "Fail: ClassWithSecondaryCtor.create";
var o4 = ClassWithMultipleSecondaryCtors.createFromString("foo3");
if (o4.x !== "fromString:foo3")
return "Fail: ClassWithMultipleSecondaryCtors.createFromString";
var o5 = ClassWithMultipleSecondaryCtors.createFromInts(1, 2);
if (o5.x !== "fromInts:1:2")
return "Fail: ClassWithMultipleSecondaryCtors.createFromInts";
var o6 = new OpenClassWithMixedConstructors("foo4");
if (o6.x !== "foo4")
return "Fail: OpenClassWithMixedConstructors";
var o7 = OpenClassWithMixedConstructors.createFromStrings("foo", "bar");
if (o7.x !== "fromStrings:foo:bar")
return "Fail: OpenClassWithMixedConstructors.createFromStrings";
var o8 = OpenClassWithMixedConstructors.createFromInts(10, -20);
if (o8.x !== "fromStrings:10:-20")
return "Fail: OpenClassWithMixedConstructors.createFromInts";
var o9 = DerivedClassWithSecondaryCtor.delegateToPrimary("foo6");
if (o9.x !== "foo6")
return "Fail: DerivedClassWithSecondaryCtor.delegateToPrimary";
var o10 = DerivedClassWithSecondaryCtor.delegateToCreateFromInts(-10, 20);
if (o10.x !== "fromStrings:-10:20")
return "Fail: DerivedClassWithSecondaryCtor.delegateToCreateFromInts";
return "OK";
}
@@ -0,0 +1,47 @@
const {
ClassWithDefaultCtor,
ClassWithPrimaryCtor,
ClassWithSecondaryCtor,
ClassWithMultipleSecondaryCtors,
DerivedClassWithSecondaryCtor,
OpenClassWithMixedConstructors
} = JS_TESTS;
function box(): string {
const o1 = new ClassWithDefaultCtor();
if (o1.x !== "ClassWithDefaultCtor::x") return "Fail: ClassWithDefaultCtor";
const o2 = new ClassWithPrimaryCtor("foo");
if (o2.x !== "foo") return "Fail: ClassWithPrimaryCtor";
const o3 = ClassWithSecondaryCtor.create("foo2");
if (o3.x !== "foo2") return "Fail: ClassWithSecondaryCtor.create";
const o4 = ClassWithMultipleSecondaryCtors.createFromString("foo3");
if (o4.x !== "fromString:foo3") return "Fail: ClassWithMultipleSecondaryCtors.createFromString";
const o5 = ClassWithMultipleSecondaryCtors.createFromInts(1, 2);
if (o5.x !== "fromInts:1:2") return "Fail: ClassWithMultipleSecondaryCtors.createFromInts";
const o6 = new OpenClassWithMixedConstructors("foo4");
if (o6.x !== "foo4") return "Fail: OpenClassWithMixedConstructors";
const o7 = OpenClassWithMixedConstructors.createFromStrings("foo", "bar");
if (o7.x !== "fromStrings:foo:bar") return "Fail: OpenClassWithMixedConstructors.createFromStrings";
const o8 = OpenClassWithMixedConstructors.createFromInts(10, -20);
if (o8.x !== "fromStrings:10:-20") return "Fail: OpenClassWithMixedConstructors.createFromInts";
const o9 = DerivedClassWithSecondaryCtor.delegateToPrimary("foo6");
if (o9.x !== "foo6") return "Fail: DerivedClassWithSecondaryCtor.delegateToPrimary";
const o10 = DerivedClassWithSecondaryCtor.delegateToCreateFromInts(-10, 20);
if (o10.x !== "fromStrings:-10:20") return "Fail: DerivedClassWithSecondaryCtor.delegateToCreateFromInts";
return "OK";
}
@@ -0,0 +1,4 @@
{
"include": [ "./*" ],
"extends": "../common.tsconfig.json"
}