[JS IR] Add literal and union types to TS generation, support them for enum
^KT-37916 fixed ^KT-44494 fixed
This commit is contained in:
@@ -94,6 +94,11 @@ sealed class ExportedType {
|
|||||||
object Nothing : Primitive("never")
|
object Nothing : Primitive("never")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sealed class LiteralType<T : Any>(val value: T) : ExportedType() {
|
||||||
|
class StringLiteralType(value: String) : LiteralType<String>(value)
|
||||||
|
class NumberLiteralType(value: Number) : LiteralType<Number>(value)
|
||||||
|
}
|
||||||
|
|
||||||
class Array(val elementType: ExportedType) : ExportedType()
|
class Array(val elementType: ExportedType) : ExportedType()
|
||||||
class Function(
|
class Function(
|
||||||
val parameterTypes: List<ExportedType>,
|
val parameterTypes: List<ExportedType>,
|
||||||
@@ -110,6 +115,8 @@ sealed class ExportedType {
|
|||||||
val members: List<ExportedDeclaration>
|
val members: List<ExportedDeclaration>
|
||||||
) : ExportedType()
|
) : ExportedType()
|
||||||
|
|
||||||
|
class UnionType(val lhs: ExportedType, val rhs: ExportedType) : ExportedType()
|
||||||
|
|
||||||
class IntersectionType(val lhs: ExportedType, val rhs: ExportedType) : ExportedType()
|
class IntersectionType(val lhs: ExportedType, val rhs: ExportedType) : ExportedType()
|
||||||
|
|
||||||
fun withNullability(nullable: Boolean) =
|
fun withNullability(nullable: Boolean) =
|
||||||
|
|||||||
+89
-9
@@ -113,11 +113,18 @@ class ExportModelGenerator(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return exportPropertyUnsafely(property)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun exportPropertyUnsafely(
|
||||||
|
property: IrProperty,
|
||||||
|
specializeType: ExportedType? = null
|
||||||
|
): ExportedDeclaration {
|
||||||
val parentClass = property.parent as? IrClass
|
val parentClass = property.parent as? IrClass
|
||||||
|
|
||||||
return ExportedProperty(
|
return ExportedProperty(
|
||||||
property.getExportedIdentifier(),
|
property.getExportedIdentifier(),
|
||||||
exportType(property.getter!!.returnType),
|
specializeType ?: exportType(property.getter!!.returnType),
|
||||||
mutable = property.isVar,
|
mutable = property.isVar,
|
||||||
isMember = parentClass != null,
|
isMember = parentClass != null,
|
||||||
isStatic = false,
|
isStatic = false,
|
||||||
@@ -128,17 +135,44 @@ class ExportModelGenerator(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun exportEnumEntry(field: IrField): ExportedProperty? {
|
private fun exportEnumEntry(field: IrField, enumEntries: List<IrEnumEntry>): ExportedProperty {
|
||||||
if (field.origin != IrDeclarationOrigin.FIELD_FOR_ENUM_ENTRY) return null
|
|
||||||
|
|
||||||
val irEnumEntry = context.mapping.fieldToEnumEntry[field]
|
val irEnumEntry = context.mapping.fieldToEnumEntry[field]
|
||||||
?: error("Unable to find enum entry for ${field.fqNameWhenAvailable}")
|
?: error("Unable to find enum entry for ${field.fqNameWhenAvailable}")
|
||||||
|
|
||||||
val parentClass = field.parent as IrClass
|
val parentClass = field.parent as IrClass
|
||||||
|
|
||||||
|
val name = irEnumEntry.getExportedIdentifier()
|
||||||
|
val ordinal = enumEntries.indexOf(irEnumEntry)
|
||||||
|
|
||||||
|
val nameProperty = ExportedProperty(
|
||||||
|
name = "name",
|
||||||
|
type = ExportedType.LiteralType.StringLiteralType(name),
|
||||||
|
mutable = false,
|
||||||
|
isMember = true,
|
||||||
|
isStatic = false,
|
||||||
|
isAbstract = false,
|
||||||
|
isProtected = false,
|
||||||
|
irGetter = null,
|
||||||
|
irSetter = null,
|
||||||
|
)
|
||||||
|
val ordinalProperty = ExportedProperty(
|
||||||
|
name = "ordinal",
|
||||||
|
type = ExportedType.LiteralType.NumberLiteralType(ordinal),
|
||||||
|
mutable = false,
|
||||||
|
isMember = true,
|
||||||
|
isStatic = false,
|
||||||
|
isAbstract = false,
|
||||||
|
isProtected = false,
|
||||||
|
irGetter = null,
|
||||||
|
irSetter = null,
|
||||||
|
)
|
||||||
|
val type = ExportedType.InlineInterfaceType(
|
||||||
|
listOf(nameProperty, ordinalProperty)
|
||||||
|
)
|
||||||
|
|
||||||
return ExportedProperty(
|
return ExportedProperty(
|
||||||
name = irEnumEntry.getExportedIdentifier(),
|
name = name,
|
||||||
type = exportType(parentClass.defaultType),
|
type = ExportedType.IntersectionType(exportType(parentClass.defaultType), type),
|
||||||
mutable = false,
|
mutable = false,
|
||||||
isMember = true,
|
isMember = true,
|
||||||
isStatic = true,
|
isStatic = true,
|
||||||
@@ -181,11 +215,25 @@ class ExportModelGenerator(
|
|||||||
|
|
||||||
val members = mutableListOf<ExportedDeclaration>()
|
val members = mutableListOf<ExportedDeclaration>()
|
||||||
val nestedClasses = mutableListOf<ExportedClass>()
|
val nestedClasses = mutableListOf<ExportedClass>()
|
||||||
|
val enumEntries = if (klass.isEnumClass) {
|
||||||
|
klass
|
||||||
|
.declarations
|
||||||
|
.filterIsInstance<IrField>()
|
||||||
|
.mapNotNull { context.mapping.fieldToEnumEntry[it] }
|
||||||
|
} else null
|
||||||
|
|
||||||
for (declaration in klass.declarations) {
|
for (declaration in klass.declarations) {
|
||||||
val candidate = getExportCandidate(declaration) ?: continue
|
val candidate = getExportCandidate(declaration) ?: continue
|
||||||
if (!shouldDeclarationBeExported(candidate, context)) continue
|
if (!shouldDeclarationBeExported(candidate, context)) continue
|
||||||
|
|
||||||
|
if (enumEntries != null) {
|
||||||
|
val enumExportedMember = exportAsEnumMember(candidate, enumEntries)
|
||||||
|
if (enumExportedMember != null) {
|
||||||
|
members.add(enumExportedMember)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
when (candidate) {
|
when (candidate) {
|
||||||
is IrSimpleFunction ->
|
is IrSimpleFunction ->
|
||||||
members.addIfNotNull(exportFunction(candidate))
|
members.addIfNotNull(exportFunction(candidate))
|
||||||
@@ -208,13 +256,11 @@ class ExportModelGenerator(
|
|||||||
is IrField -> {
|
is IrField -> {
|
||||||
assert(
|
assert(
|
||||||
candidate.origin == IrDeclarationOrigin.FIELD_FOR_OBJECT_INSTANCE ||
|
candidate.origin == IrDeclarationOrigin.FIELD_FOR_OBJECT_INSTANCE ||
|
||||||
candidate.origin == IrDeclarationOrigin.FIELD_FOR_ENUM_ENTRY
|
candidate.origin == IrDeclarationOrigin.FIELD_FOR_OUTER_THIS
|
||||||
|| candidate.origin == IrDeclarationOrigin.FIELD_FOR_OUTER_THIS
|
|
||||||
|| candidate.correspondingPropertySymbol != null
|
|| candidate.correspondingPropertySymbol != null
|
||||||
) {
|
) {
|
||||||
"Unexpected field without property ${candidate.fqNameWhenAvailable}"
|
"Unexpected field without property ${candidate.fqNameWhenAvailable}"
|
||||||
}
|
}
|
||||||
members.addIfNotNull(exportEnumEntry(candidate))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> error("Can't export member declaration $declaration")
|
else -> error("Can't export member declaration $declaration")
|
||||||
@@ -271,6 +317,40 @@ class ExportModelGenerator(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun exportAsEnumMember(candidate: IrDeclarationWithName, enumEntries: List<IrEnumEntry>): ExportedDeclaration? {
|
||||||
|
return when (candidate) {
|
||||||
|
is IrProperty -> {
|
||||||
|
if (candidate.isEnumFakeOverriddenDeclaration(context)) {
|
||||||
|
val type: ExportedType? = when (candidate.getExportedIdentifier()) {
|
||||||
|
"name" -> enumEntries
|
||||||
|
.map { it.getExportedIdentifier() }
|
||||||
|
.map { ExportedType.LiteralType.StringLiteralType(it) }
|
||||||
|
.reduce { acc: ExportedType, s: ExportedType -> ExportedType.UnionType(acc, s) }
|
||||||
|
"ordinal" -> enumEntries
|
||||||
|
.map { enumEntries.indexOf(it) }
|
||||||
|
.map { ExportedType.LiteralType.NumberLiteralType(it) }
|
||||||
|
.reduce { acc: ExportedType, s: ExportedType -> ExportedType.UnionType(acc, s) }
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
exportPropertyUnsafely(
|
||||||
|
candidate,
|
||||||
|
type
|
||||||
|
)
|
||||||
|
} else null
|
||||||
|
}
|
||||||
|
|
||||||
|
is IrField -> {
|
||||||
|
if (candidate.origin == IrDeclarationOrigin.FIELD_FOR_ENUM_ENTRY) {
|
||||||
|
exportEnumEntry(candidate, enumEntries)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun IrType.canBeUsedAsSuperTypeOfExportedClasses(): Boolean =
|
private fun IrType.canBeUsedAsSuperTypeOfExportedClasses(): Boolean =
|
||||||
!this.isAny() && classifierOrNull != context.irBuiltIns.enumClass
|
!this.isAny() && classifierOrNull != context.irBuiltIns.enumClass
|
||||||
|
|
||||||
|
|||||||
+5
@@ -230,4 +230,9 @@ fun ExportedType.toTypeScript(indent: String): String = when (this) {
|
|||||||
is ExportedType.IntersectionType -> {
|
is ExportedType.IntersectionType -> {
|
||||||
lhs.toTypeScript(indent) + " & " + rhs.toTypeScript(indent)
|
lhs.toTypeScript(indent) + " & " + rhs.toTypeScript(indent)
|
||||||
}
|
}
|
||||||
|
is ExportedType.UnionType -> {
|
||||||
|
lhs.toTypeScript(indent) + " | " + rhs.toTypeScript(indent)
|
||||||
|
}
|
||||||
|
is ExportedType.LiteralType.StringLiteralType -> "\"$value\""
|
||||||
|
is ExportedType.LiteralType.NumberLiteralType -> value.toString()
|
||||||
}
|
}
|
||||||
+10
-4
@@ -101,15 +101,21 @@ declare namespace JS_TESTS {
|
|||||||
class TestEnumClass {
|
class TestEnumClass {
|
||||||
private constructor();
|
private constructor();
|
||||||
readonly constructorParameter: string;
|
readonly constructorParameter: string;
|
||||||
static readonly A: foo.TestEnumClass;
|
static readonly A: foo.TestEnumClass & {
|
||||||
static readonly B: foo.TestEnumClass;
|
readonly name: "A";
|
||||||
|
readonly ordinal: 0;
|
||||||
|
};
|
||||||
|
static readonly B: foo.TestEnumClass & {
|
||||||
|
readonly name: "B";
|
||||||
|
readonly ordinal: 1;
|
||||||
|
};
|
||||||
readonly foo: number;
|
readonly foo: number;
|
||||||
bar(value: string): string;
|
bar(value: string): string;
|
||||||
bay(): string;
|
bay(): string;
|
||||||
static values(): Array<foo.TestEnumClass>;
|
static values(): Array<foo.TestEnumClass>;
|
||||||
static valueOf(value: string): foo.TestEnumClass;
|
static valueOf(value: string): foo.TestEnumClass;
|
||||||
readonly name: string;
|
readonly name: "A" | "B";
|
||||||
readonly ordinal: number;
|
readonly ordinal: 0 | 1;
|
||||||
}
|
}
|
||||||
namespace TestEnumClass {
|
namespace TestEnumClass {
|
||||||
class Nested {
|
class Nested {
|
||||||
|
|||||||
Reference in New Issue
Block a user