feat(@JsExport for interfaces): add ability to export interfaces in TypeScript.
This commit is contained in:
@@ -106,7 +106,7 @@ sealed class ExportedType {
|
||||
val returnType: ExportedType
|
||||
) : ExportedType()
|
||||
|
||||
class ClassType(val name: String, val arguments: List<ExportedType>) : ExportedType()
|
||||
class ClassType(val name: String, val arguments: List<ExportedType>, val ir: IrClass) : ExportedType()
|
||||
class TypeParameter(val name: String) : ExportedType()
|
||||
class Nullable(val baseType: ExportedType) : ExportedType()
|
||||
class ErrorType(val comment: String) : ExportedType()
|
||||
|
||||
+2
-1
@@ -483,7 +483,8 @@ class ExportModelGenerator(
|
||||
ClassKind.ENUM_CLASS,
|
||||
ClassKind.INTERFACE -> ExportedType.ClassType(
|
||||
name,
|
||||
type.arguments.map { exportTypeArgument(it) }
|
||||
type.arguments.map { exportTypeArgument(it) },
|
||||
klass
|
||||
)
|
||||
}.withImplicitlyExported(isImplicitlyExported)
|
||||
}
|
||||
|
||||
+38
-8
@@ -19,7 +19,15 @@ fun ExportedModule.toTypeScript(): String {
|
||||
}
|
||||
|
||||
fun wrapTypeScript(name: String, moduleKind: ModuleKind, dts: String): String {
|
||||
val types = "${moduleKind.indent}type Nullable<T> = T | null | undefined\n"
|
||||
val declareKeyword = when (moduleKind) {
|
||||
ModuleKind.PLAIN -> ""
|
||||
else -> "declare "
|
||||
}
|
||||
val types = """
|
||||
type Nullable<T> = T | null | undefined
|
||||
${declareKeyword}const __doNotImplementIt: unique symbol
|
||||
type __doNotImplementIt = typeof __doNotImplementIt
|
||||
""".trimIndent().prependIndent(moduleKind.indent) + "\n"
|
||||
|
||||
val declarationsDts = types + dts
|
||||
|
||||
@@ -117,14 +125,16 @@ fun ExportedDeclaration.toTypeScript(indent: String, prefix: String = ""): Strin
|
||||
val superClassClause = superClass?.let { it.toExtendsClause(indent) } ?: ""
|
||||
val superInterfacesClause = superInterfaces.toImplementsClause(superInterfacesKeyword, indent)
|
||||
|
||||
val members = members.map {
|
||||
if (!ir.isInner || it !is ExportedFunction || !it.isStatic) {
|
||||
it
|
||||
} else {
|
||||
// Remove $outer argument from secondary constructors of inner classes
|
||||
it.copy(parameters = it.parameters.drop(1))
|
||||
val members = members
|
||||
.let { if (shouldNotBeImplemented()) it.withMagicProperty() else it }
|
||||
.map {
|
||||
if (!ir.isInner || it !is ExportedFunction || !it.isStatic) {
|
||||
it
|
||||
} else {
|
||||
// Remove $outer argument from secondary constructors of inner classes
|
||||
it.copy(parameters = it.parameters.drop(1))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val (innerClasses, nonInnerClasses) = nestedClasses.partition { it.ir.isInner }
|
||||
val innerClassesProperties = innerClasses.map { it.toReadonlyProperty() }
|
||||
@@ -179,6 +189,26 @@ fun List<ExportedType>.toImplementsClause(superInterfacesKeyword: String, indent
|
||||
}
|
||||
}
|
||||
|
||||
fun ExportedClass.shouldNotBeImplemented(): Boolean {
|
||||
return (isInterface && !ir.isExternal) || superInterfaces.any { it is ExportedType.ClassType && !it.ir.isExternal }
|
||||
}
|
||||
|
||||
fun List<ExportedDeclaration>.withMagicProperty(): List<ExportedDeclaration> {
|
||||
return plus(
|
||||
ExportedProperty(
|
||||
"__doNotUseIt",
|
||||
ExportedType.TypeParameter("__doNotImplementIt"),
|
||||
mutable = false,
|
||||
isMember = true,
|
||||
isStatic = false,
|
||||
isAbstract = false,
|
||||
isProtected = false,
|
||||
irGetter = null,
|
||||
irSetter = null
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
fun IrClass.asNestedClassAccess(): String {
|
||||
val name = getJsNameOrKotlinName().identifier
|
||||
if (parent !is IrClass) return name
|
||||
|
||||
+51
-31
@@ -6,6 +6,7 @@
|
||||
package org.jetbrains.kotlin.ir.backend.js.transformers.irToJs
|
||||
|
||||
import org.jetbrains.kotlin.descriptors.DescriptorVisibilities
|
||||
import org.jetbrains.kotlin.descriptors.DescriptorVisibility
|
||||
import org.jetbrains.kotlin.descriptors.Modality
|
||||
import org.jetbrains.kotlin.ir.backend.js.export.isEnumFakeOverriddenDeclaration
|
||||
import org.jetbrains.kotlin.ir.backend.js.export.isExported
|
||||
@@ -125,24 +126,21 @@ class JsClassGenerator(private val irClass: IrClass, val context: JsGenerationCo
|
||||
)
|
||||
}
|
||||
|
||||
fun IrSimpleFunction.accessorRef(): JsNameRef? =
|
||||
when (visibility) {
|
||||
DescriptorVisibilities.PRIVATE -> null
|
||||
else -> JsNameRef(
|
||||
context.getNameForMemberFunction(this),
|
||||
classPrototypeRef
|
||||
)
|
||||
}
|
||||
val overriddenSymbols = property.getter?.overriddenSymbols.orEmpty()
|
||||
|
||||
// Don't generate `defineProperty` if the property overrides a property from an exported class,
|
||||
// because we've already generated `defineProperty` for the base class property.
|
||||
// In other words, we only want to generate `defineProperty` once for each property.
|
||||
// The exception is case when we override val with var,
|
||||
// so we need regenerate `defineProperty` with setter.
|
||||
val noOverriddenGetter = property.getter?.overriddenSymbols?.isEmpty() == true
|
||||
// P.S. If the overridden property is owned by an interface - we should generate defineProperty
|
||||
// for overridden property in the first class which override those properties
|
||||
val hasOverriddenExportedInterfaceProperties = overriddenSymbols.any { it.owner.parentClassOrNull.isExportedInterface() }
|
||||
&& !overriddenSymbols.any { it.owner.parentClassOrNull.isExportedClass() }
|
||||
|
||||
val overriddenExportedGetter = (property.getter?.overriddenSymbols?.isNotEmpty() == true &&
|
||||
property.getter?.isOverriddenExported(context.staticContext.backendContext) == true)
|
||||
val getterOverridesExternal = property.getter?.overridesExternal() == true
|
||||
val overriddenExportedGetter = !property.getter?.overriddenSymbols.isNullOrEmpty() &&
|
||||
property.getter?.isOverriddenExported(context.staticContext.backendContext) == true
|
||||
|
||||
val noOverriddenExportedSetter = property.setter?.isOverriddenExported(context.staticContext.backendContext) == false
|
||||
|
||||
@@ -150,8 +148,9 @@ class JsClassGenerator(private val irClass: IrClass, val context: JsGenerationCo
|
||||
property.isEnumFakeOverriddenDeclaration(context.staticContext.backendContext)
|
||||
|
||||
if (irClass.isExported(context.staticContext.backendContext) &&
|
||||
(noOverriddenGetter || needsOverride) ||
|
||||
property.getter?.overridesExternal() == true ||
|
||||
(overriddenSymbols.isEmpty() || needsOverride) ||
|
||||
hasOverriddenExportedInterfaceProperties ||
|
||||
getterOverridesExternal ||
|
||||
property.getJsName() != null
|
||||
) {
|
||||
|
||||
@@ -168,31 +167,24 @@ class JsClassGenerator(private val irClass: IrClass, val context: JsGenerationCo
|
||||
// set: Foo.prototype._set_prop__a4enbm_k$
|
||||
// });
|
||||
|
||||
val getterForwarder = if (property.getter?.modality == Modality.FINAL) property.getter?.accessorRef()
|
||||
else {
|
||||
property.getter?.propertyAccessorForwarder("getter forwarder") { getterRef ->
|
||||
JsReturn(
|
||||
JsInvocation(
|
||||
getterRef
|
||||
)
|
||||
)
|
||||
val getterForwarder = property.getter
|
||||
.takeIf { it.shouldExportAccessor() }
|
||||
.getOrGenerateIfFinal {
|
||||
propertyAccessorForwarder("getter forwarder") {
|
||||
JsReturn(JsInvocation(it))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val setterForwarder = if (property.setter?.modality == Modality.FINAL) property.setter?.accessorRef()
|
||||
else {
|
||||
property.setter?.let {
|
||||
val setterForwarder = property.setter
|
||||
.takeIf { it.shouldExportAccessor() }
|
||||
.getOrGenerateIfFinal {
|
||||
val setterArgName = JsName("value", false)
|
||||
it.propertyAccessorForwarder("setter forwarder") { setterRef ->
|
||||
JsInvocation(
|
||||
setterRef,
|
||||
JsNameRef(setterArgName)
|
||||
).makeStmt()
|
||||
propertyAccessorForwarder("setter forwarder") {
|
||||
JsInvocation(it, JsNameRef(setterArgName)).makeStmt()
|
||||
}?.apply {
|
||||
parameters.add(JsParameter(setterArgName))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
classBlock.statements += JsExpressionStatement(
|
||||
defineProperty(
|
||||
@@ -209,6 +201,34 @@ class JsClassGenerator(private val irClass: IrClass, val context: JsGenerationCo
|
||||
return classBlock
|
||||
}
|
||||
|
||||
private inline fun IrSimpleFunction?.getOrGenerateIfFinal(generateFunc: IrSimpleFunction.() -> JsFunction?): JsExpression? {
|
||||
if (this == null) return null
|
||||
return if (modality == Modality.FINAL) accessorRef() else generateFunc()
|
||||
}
|
||||
|
||||
private fun IrSimpleFunction?.shouldExportAccessor(): Boolean {
|
||||
if (this == null) return false
|
||||
|
||||
if (parentAsClass.isExported(context.staticContext.backendContext)) return true
|
||||
|
||||
val property = correspondingPropertySymbol!!.owner
|
||||
|
||||
if (property.isOverriddenExported(context.staticContext.backendContext)) {
|
||||
return isOverriddenExported(context.staticContext.backendContext)
|
||||
}
|
||||
|
||||
return overridesExternal() || property.getJsName() != null
|
||||
}
|
||||
|
||||
private fun IrSimpleFunction.accessorRef(): JsNameRef? =
|
||||
when (visibility) {
|
||||
DescriptorVisibilities.PRIVATE -> null
|
||||
else -> JsNameRef(
|
||||
context.getNameForMemberFunction(this),
|
||||
classPrototypeRef
|
||||
)
|
||||
}
|
||||
|
||||
private fun IrSimpleFunction.generateAssignmentIfMangled(memberRef: JsExpression) {
|
||||
if (
|
||||
irClass.isExported(context.staticContext.backendContext) &&
|
||||
|
||||
+6
-3
@@ -143,11 +143,14 @@ fun translateCall(
|
||||
val jsExtensionReceiver = expression.extensionReceiver?.accept(transformer, context)
|
||||
val arguments = translateCallArguments(expression, context, transformer)
|
||||
|
||||
// Transform external property accessor call
|
||||
// @JsName-annotated external property accessors are translated as function calls
|
||||
// Transform external and interface's property accessor call
|
||||
// @JsName-annotated external and interface's property accessors are translated as function calls
|
||||
if (function.getJsName() == null) {
|
||||
val property = function.correspondingPropertySymbol?.owner
|
||||
if (property != null && property.isEffectivelyExternal()) {
|
||||
if (
|
||||
property != null &&
|
||||
(property.isEffectivelyExternal() || property.isExportedInterfaceMember())
|
||||
) {
|
||||
val propertyName = context.getNameForProperty(property)
|
||||
val nameRef = when (jsDispatchReceiver) {
|
||||
null -> jsGlobalVarRef(JsNameRef(propertyName))
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* Copyright 2010-2021 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.utils
|
||||
|
||||
import org.jetbrains.kotlin.descriptors.isInterface
|
||||
import org.jetbrains.kotlin.descriptors.isClass
|
||||
import org.jetbrains.kotlin.ir.declarations.IrClass
|
||||
import org.jetbrains.kotlin.ir.declarations.IrDeclaration
|
||||
import org.jetbrains.kotlin.ir.util.parentClassOrNull
|
||||
|
||||
fun IrDeclaration?.isExportedClass() =
|
||||
this is IrClass && kind.isClass && isJsExport()
|
||||
|
||||
fun IrDeclaration?.isExportedInterface() =
|
||||
this is IrClass && kind.isInterface && isJsExport()
|
||||
|
||||
fun IrDeclaration.isExportedInterfaceMember() =
|
||||
parentClassOrNull.isExportedInterface()
|
||||
+1
-1
@@ -20,7 +20,7 @@ open class ExportedGenericClass<T>
|
||||
class <!NON_EXPORTABLE_TYPE("super; ExportedGenericClass<NonExportedClass>")!>ExportedClass3<!> : ExportedGenericClass<NonExportedClass>()
|
||||
|
||||
@JsExport
|
||||
interface <!WRONG_EXPORTED_DECLARATION!>ExportedGenericInterface<!><T>
|
||||
interface ExportedGenericInterface<T>
|
||||
|
||||
@JsExport
|
||||
class <!NON_EXPORTABLE_TYPE("super; ExportedGenericInterface<NonExportedClass>")!>ExportedClass4<!> : ExportedGenericInterface<NonExportedClass>
|
||||
|
||||
Vendored
+1
-1
@@ -14,4 +14,4 @@ fun <<!NON_EXPORTABLE_TYPE("upper bound; C")!>T : C<!>>foo() { }
|
||||
class A<<!NON_EXPORTABLE_TYPE("upper bound; C")!>T : C<!>, <!NON_EXPORTABLE_TYPE("upper bound; I")!>S: I<!>>
|
||||
|
||||
@JsExport
|
||||
interface <!WRONG_EXPORTED_DECLARATION!>I2<!><<!NON_EXPORTABLE_TYPE("upper bound; C"), NON_EXPORTABLE_TYPE("upper bound; I")!>T<!>> where T : C, T : I
|
||||
interface I2<<!NON_EXPORTABLE_TYPE("upper bound; C"), NON_EXPORTABLE_TYPE("upper bound; I")!>T<!>> where T : C, T : I
|
||||
|
||||
+13
-1
@@ -18,11 +18,23 @@ val String.extensionProperty<!>
|
||||
annotation class <!WRONG_EXPORTED_DECLARATION("annotation class")!>AnnotationClass<!>
|
||||
|
||||
@JsExport
|
||||
interface <!WRONG_EXPORTED_DECLARATION("interface")!>SomeInterface<!>
|
||||
interface SomeInterface
|
||||
|
||||
@JsExport
|
||||
external interface GoodInterface
|
||||
|
||||
@JsExport
|
||||
interface InterfaceWithCompanion {
|
||||
companion <!WRONG_EXPORTED_DECLARATION("companion object inside exported interface")!>object<!> {
|
||||
fun foo() = 42
|
||||
}
|
||||
}
|
||||
|
||||
@JsExport
|
||||
interface OuterInterface {
|
||||
class <!WRONG_EXPORTED_DECLARATION("nested class inside exported interface")!>Nested<!>
|
||||
}
|
||||
|
||||
@JsExport
|
||||
value class <!WRONG_EXPORTED_DECLARATION("value class")!>A(val a: Int)<!>
|
||||
|
||||
|
||||
+27
@@ -50,6 +50,33 @@ package foo {
|
||||
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
|
||||
}
|
||||
|
||||
@kotlin.js.JsExport public interface InterfaceWithCompanion {
|
||||
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
|
||||
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
|
||||
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
|
||||
|
||||
public companion object Companion {
|
||||
private constructor Companion()
|
||||
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
|
||||
public final fun foo(): kotlin.Int
|
||||
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
|
||||
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
|
||||
}
|
||||
}
|
||||
|
||||
@kotlin.js.JsExport public interface OuterInterface {
|
||||
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
|
||||
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
|
||||
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
|
||||
|
||||
public final class Nested {
|
||||
public constructor Nested()
|
||||
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
|
||||
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
|
||||
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
|
||||
}
|
||||
}
|
||||
|
||||
@kotlin.js.JsExport public interface SomeInterface {
|
||||
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
|
||||
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
|
||||
|
||||
Reference in New Issue
Block a user