From 9fdb0a23cde45e4d84f3ef7844193b25d14cfd67 Mon Sep 17 00:00:00 2001 From: Ilya Goncharov Date: Fri, 29 Oct 2021 12:54:07 +0300 Subject: [PATCH] [JS IR] Add literal and union types to TS generation, support them for enum ^KT-37916 fixed ^KT-44494 fixed --- .../ir/backend/js/export/ExportModel.kt | 7 ++ .../backend/js/export/ExportModelGenerator.kt | 98 +++++++++++++++++-- .../js/export/ExportModelToTsDeclarations.kt | 5 + .../declarations/declarations.d.ts | 14 ++- 4 files changed, 111 insertions(+), 13 deletions(-) diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModel.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModel.kt index efe2b7ddf37..617ab447c56 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModel.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModel.kt @@ -94,6 +94,11 @@ sealed class ExportedType { object Nothing : Primitive("never") } + sealed class LiteralType(val value: T) : ExportedType() { + class StringLiteralType(value: String) : LiteralType(value) + class NumberLiteralType(value: Number) : LiteralType(value) + } + class Array(val elementType: ExportedType) : ExportedType() class Function( val parameterTypes: List, @@ -110,6 +115,8 @@ sealed class ExportedType { val members: List ) : ExportedType() + class UnionType(val lhs: ExportedType, val rhs: ExportedType) : ExportedType() + class IntersectionType(val lhs: ExportedType, val rhs: ExportedType) : ExportedType() fun withNullability(nullable: Boolean) = diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModelGenerator.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModelGenerator.kt index 7fd590d3a24..5f1c5457614 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModelGenerator.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModelGenerator.kt @@ -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): 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() val nestedClasses = mutableListOf() + val enumEntries = if (klass.isEnumClass) { + klass + .declarations + .filterIsInstance() + .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): 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 diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModelToTsDeclarations.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModelToTsDeclarations.kt index 18a21cf0cc8..1227c3d4932 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModelToTsDeclarations.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModelToTsDeclarations.kt @@ -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() } \ No newline at end of file diff --git a/js/js.translator/testData/typescript-export/declarations/declarations.d.ts b/js/js.translator/testData/typescript-export/declarations/declarations.d.ts index 7bd18d20eac..18d33836100 100644 --- a/js/js.translator/testData/typescript-export/declarations/declarations.d.ts +++ b/js/js.translator/testData/typescript-export/declarations/declarations.d.ts @@ -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; static valueOf(value: string): foo.TestEnumClass; - readonly name: string; - readonly ordinal: number; + readonly name: "A" | "B"; + readonly ordinal: 0 | 1; } namespace TestEnumClass { class Nested {