[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:
Ilya Goncharov
2021-10-29 12:54:07 +03:00
committed by Space
parent 4fa2fa6c26
commit 9fdb0a23cd
4 changed files with 111 additions and 13 deletions
@@ -94,6 +94,11 @@ sealed class ExportedType {
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 Function(
val parameterTypes: List<ExportedType>,
@@ -110,6 +115,8 @@ sealed class ExportedType {
val members: List<ExportedDeclaration>
) : ExportedType()
class UnionType(val lhs: ExportedType, val rhs: ExportedType) : ExportedType()
class IntersectionType(val lhs: ExportedType, val rhs: ExportedType) : ExportedType()
fun withNullability(nullable: Boolean) =
@@ -113,11 +113,18 @@ class ExportModelGenerator(
}
}
return exportPropertyUnsafely(property)
}
private fun exportPropertyUnsafely(
property: IrProperty,
specializeType: ExportedType? = null
): ExportedDeclaration {
val parentClass = property.parent as? IrClass
return ExportedProperty(
property.getExportedIdentifier(),
exportType(property.getter!!.returnType),
specializeType ?: exportType(property.getter!!.returnType),
mutable = property.isVar,
isMember = parentClass != null,
isStatic = false,
@@ -128,17 +135,44 @@ class ExportModelGenerator(
)
}
private fun exportEnumEntry(field: IrField): ExportedProperty? {
if (field.origin != IrDeclarationOrigin.FIELD_FOR_ENUM_ENTRY) return null
private fun exportEnumEntry(field: IrField, enumEntries: List<IrEnumEntry>): ExportedProperty {
val irEnumEntry = context.mapping.fieldToEnumEntry[field]
?: error("Unable to find enum entry for ${field.fqNameWhenAvailable}")
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(
name = irEnumEntry.getExportedIdentifier(),
type = exportType(parentClass.defaultType),
name = name,
type = ExportedType.IntersectionType(exportType(parentClass.defaultType), type),
mutable = false,
isMember = true,
isStatic = true,
@@ -181,11 +215,25 @@ class ExportModelGenerator(
val members = mutableListOf<ExportedDeclaration>()
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) {
val candidate = getExportCandidate(declaration) ?: continue
if (!shouldDeclarationBeExported(candidate, context)) continue
if (enumEntries != null) {
val enumExportedMember = exportAsEnumMember(candidate, enumEntries)
if (enumExportedMember != null) {
members.add(enumExportedMember)
continue
}
}
when (candidate) {
is IrSimpleFunction ->
members.addIfNotNull(exportFunction(candidate))
@@ -208,13 +256,11 @@ class ExportModelGenerator(
is IrField -> {
assert(
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
) {
"Unexpected field without property ${candidate.fqNameWhenAvailable}"
}
members.addIfNotNull(exportEnumEntry(candidate))
}
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 =
!this.isAny() && classifierOrNull != context.irBuiltIns.enumClass
@@ -230,4 +230,9 @@ fun ExportedType.toTypeScript(indent: String): String = when (this) {
is ExportedType.IntersectionType -> {
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()
}
@@ -101,15 +101,21 @@ declare namespace JS_TESTS {
class TestEnumClass {
private constructor();
readonly constructorParameter: string;
static readonly A: foo.TestEnumClass;
static readonly B: foo.TestEnumClass;
static readonly A: foo.TestEnumClass & {
readonly name: "A";
readonly ordinal: 0;
};
static readonly B: foo.TestEnumClass & {
readonly name: "B";
readonly ordinal: 1;
};
readonly foo: number;
bar(value: string): string;
bay(): string;
static values(): Array<foo.TestEnumClass>;
static valueOf(value: string): foo.TestEnumClass;
readonly name: string;
readonly ordinal: number;
readonly name: "A" | "B";
readonly ordinal: 0 | 1;
}
namespace TestEnumClass {
class Nested {