From 0bb0be870351f274b8fcdbdef5cf12a2dc9833fc Mon Sep 17 00:00:00 2001 From: Artem Kobzar Date: Fri, 2 Sep 2022 10:50:20 +0000 Subject: [PATCH] feat(KT-48814): represent all of the external declarations nullable properties as an optional fields inside d.ts. --- .../kotlin/ir/backend/js/export/ExportModel.kt | 1 + .../ir/backend/js/export/ExportModelGenerator.kt | 6 +++++- .../backend/js/export/ExportModelToTsDeclarations.kt | 3 ++- .../kotlin/js/resolve/JsPlatformConfigurator.kt | 2 +- .../typescript-export/inheritance/inheritance.d.ts | 4 ++-- .../typescript-export/interfaces/interfaces.d.ts | 7 +++++++ .../typescript-export/interfaces/interfaces.kt | 11 +++++++++++ .../typescript-export/interfaces/interfaces__main.js | 4 ++++ .../typescript-export/interfaces/interfaces__main.ts | 5 +++++ .../js-name-in-exported-file/js-name.d.ts | 2 +- .../testData/typescript-export/js-name/js-name.d.ts | 2 +- libraries/stdlib/js/src/kotlin/annotations.kt | 2 +- 12 files changed, 41 insertions(+), 8 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 1934ff46680..7a976176c3e 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 @@ -59,6 +59,7 @@ class ExportedProperty( val isField: Boolean = false, val irGetter: IrFunction? = null, val irSetter: IrFunction? = null, + val isOptional: Boolean = false ) : ExportedDeclaration() // TODO: Cover all cases with frontend and disable error declarations 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 f02ad939b1c..5d9bcb55d7f 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 @@ -139,6 +139,9 @@ class ExportModelGenerator(val context: JsIrBackendContext, val generateNamespac specializeType: ExportedType? = null ): ExportedDeclaration { val parentClass = property.parent as? IrClass + val isOptional = property.isEffectivelyExternal() && + property.parent is IrClass && + property.getter?.returnType?.isNullable() == true return ExportedProperty( name = property.getExportedIdentifier(), @@ -149,7 +152,8 @@ class ExportModelGenerator(val context: JsIrBackendContext, val generateNamespac isProtected = property.visibility == DescriptorVisibilities.PROTECTED, isField = parentClass?.isInterface == true, irGetter = property.getter, - irSetter = property.setter + irSetter = property.setter, + isOptional = isOptional ) } 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 d36d55771e2..dd78d6b3800 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 @@ -133,7 +133,8 @@ class ExportModelToTsDeclarations { "" } else { val readonly = if (isMember && !mutable) "readonly " else "" - "$prefix$visibility$possibleStatic$keyword$readonly$memberName: $typeToTypeScript;" + val optional = if (isOptional) "?" else "" + "$prefix$visibility$possibleStatic$keyword$readonly$memberName$optional: $typeToTypeScript;" } } } diff --git a/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/JsPlatformConfigurator.kt b/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/JsPlatformConfigurator.kt index 1457b9f85e1..e8a473c028e 100644 --- a/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/JsPlatformConfigurator.kt +++ b/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/JsPlatformConfigurator.kt @@ -25,7 +25,7 @@ object JsPlatformConfigurator : PlatformConfiguratorBase( JsRuntimeAnnotationChecker, JsDynamicDeclarationChecker, JsExportAnnotationChecker, - JsExportDeclarationChecker + JsExportDeclarationChecker, ), additionalCallCheckers = listOf( JsModuleCallChecker, diff --git a/js/js.translator/testData/typescript-export/inheritance/inheritance.d.ts b/js/js.translator/testData/typescript-export/inheritance/inheritance.d.ts index 65c6c307d22..dc722ef79c0 100644 --- a/js/js.translator/testData/typescript-export/inheritance/inheritance.d.ts +++ b/js/js.translator/testData/typescript-export/inheritance/inheritance.d.ts @@ -2,8 +2,8 @@ declare namespace JS_TESTS { type Nullable = T | null | undefined namespace foo { interface I { - x: T; - readonly y: S; + x?: T; + readonly y?: S; z(u: U): void; } interface I2 { diff --git a/js/js.translator/testData/typescript-export/interfaces/interfaces.d.ts b/js/js.translator/testData/typescript-export/interfaces/interfaces.d.ts index 06fd2f146aa..7a8cadd01cc 100644 --- a/js/js.translator/testData/typescript-export/interfaces/interfaces.d.ts +++ b/js/js.translator/testData/typescript-export/interfaces/interfaces.d.ts @@ -1,5 +1,11 @@ declare namespace JS_TESTS { type Nullable = T | null | undefined + namespace foo { + interface OptionalFieldsInterface { + readonly required: number; + readonly notRequired?: Nullable; + } + } namespace foo { interface TestInterface { readonly value: string; @@ -24,5 +30,6 @@ declare namespace JS_TESTS { readonly __doNotUseOrImplementIt: foo.TestInterfaceImpl["__doNotUseOrImplementIt"] & foo.AnotherExportedInterface["__doNotUseOrImplementIt"]; } function processInterface(test: foo.TestInterface): string; + function processOptionalInterface(a: foo.OptionalFieldsInterface): string; } } \ No newline at end of file diff --git a/js/js.translator/testData/typescript-export/interfaces/interfaces.kt b/js/js.translator/testData/typescript-export/interfaces/interfaces.kt index c334ef8a706..caf5f7d364f 100644 --- a/js/js.translator/testData/typescript-export/interfaces/interfaces.kt +++ b/js/js.translator/testData/typescript-export/interfaces/interfaces.kt @@ -30,4 +30,15 @@ class ChildTestInterfaceImpl(): TestInterfaceImpl("Test"), AnotherExportedInterf @JsExport fun processInterface(test: TestInterface): String { return "Owner ${test.getOwnerName()} has value '${test.value}'" +} + +@JsExport +external interface OptionalFieldsInterface { + val required: Int + val notRequired: Int? +} + +@JsExport +fun processOptionalInterface(a: OptionalFieldsInterface): String { + return "${a.required}${a.notRequired ?: "unknown"}" } \ No newline at end of file diff --git a/js/js.translator/testData/typescript-export/interfaces/interfaces__main.js b/js/js.translator/testData/typescript-export/interfaces/interfaces__main.js index f3aba8a8c27..72a187b6c99 100644 --- a/js/js.translator/testData/typescript-export/interfaces/interfaces__main.js +++ b/js/js.translator/testData/typescript-export/interfaces/interfaces__main.js @@ -2,6 +2,7 @@ var TestInterfaceImpl = JS_TESTS.foo.TestInterfaceImpl; var ChildTestInterfaceImpl = JS_TESTS.foo.ChildTestInterfaceImpl; var processInterface = JS_TESTS.foo.processInterface; +var processOptionalInterface = JS_TESTS.foo.processOptionalInterface; function assert(condition) { if (!condition) { throw "Assertion failed"; @@ -12,5 +13,8 @@ function box() { assert(processInterface(new ChildTestInterfaceImpl()) === "Owner TestInterfaceImpl has value 'Test'"); // @ts-expect-error "Just test that this code will throw compilation error for a user" assert(processInterface({ value: "bar", getOwnerName: function () { return "RandomObject"; } }) === "Owner RandomObject has value 'bar'"); + assert(processOptionalInterface({ required: 4 }) == "4unknown"); + assert(processOptionalInterface({ required: 4, notRequired: null }) == "4unknown"); + assert(processOptionalInterface({ required: 4, notRequired: 5 }) == "45"); return "OK"; } diff --git a/js/js.translator/testData/typescript-export/interfaces/interfaces__main.ts b/js/js.translator/testData/typescript-export/interfaces/interfaces__main.ts index c16a4278309..de5842b2b27 100644 --- a/js/js.translator/testData/typescript-export/interfaces/interfaces__main.ts +++ b/js/js.translator/testData/typescript-export/interfaces/interfaces__main.ts @@ -1,6 +1,7 @@ import TestInterfaceImpl = JS_TESTS.foo.TestInterfaceImpl; import ChildTestInterfaceImpl = JS_TESTS.foo.ChildTestInterfaceImpl; import processInterface = JS_TESTS.foo.processInterface; +import processOptionalInterface = JS_TESTS.foo.processOptionalInterface; function assert(condition: boolean) { if (!condition) { @@ -15,5 +16,9 @@ function box(): string { // @ts-expect-error "Just test that this code will throw compilation error for a user" assert(processInterface({ value: "bar", getOwnerName: () => "RandomObject" }) === "Owner RandomObject has value 'bar'") + assert(processOptionalInterface({ required: 4 }) == "4unknown") + assert(processOptionalInterface({ required: 4, notRequired: null }) == "4unknown") + assert(processOptionalInterface({ required: 4, notRequired: 5 }) == "45") + return "OK"; } \ No newline at end of file diff --git a/js/js.translator/testData/typescript-export/js-name-in-exported-file/js-name.d.ts b/js/js.translator/testData/typescript-export/js-name-in-exported-file/js-name.d.ts index 5f9b219bd5a..31eb65fdb06 100644 --- a/js/js.translator/testData/typescript-export/js-name-in-exported-file/js-name.d.ts +++ b/js/js.translator/testData/typescript-export/js-name-in-exported-file/js-name.d.ts @@ -2,7 +2,7 @@ declare namespace JS_TESTS { type Nullable = T | null | undefined namespace foo { interface Object { - readonly constructor: any; + readonly constructor?: any; } } namespace foo { diff --git a/js/js.translator/testData/typescript-export/js-name/js-name.d.ts b/js/js.translator/testData/typescript-export/js-name/js-name.d.ts index 5f9b219bd5a..31eb65fdb06 100644 --- a/js/js.translator/testData/typescript-export/js-name/js-name.d.ts +++ b/js/js.translator/testData/typescript-export/js-name/js-name.d.ts @@ -2,7 +2,7 @@ declare namespace JS_TESTS { type Nullable = T | null | undefined namespace foo { interface Object { - readonly constructor: any; + readonly constructor?: any; } } namespace foo { diff --git a/libraries/stdlib/js/src/kotlin/annotations.kt b/libraries/stdlib/js/src/kotlin/annotations.kt index 9754955bb25..b43046a87e2 100644 --- a/libraries/stdlib/js/src/kotlin/annotations.kt +++ b/libraries/stdlib/js/src/kotlin/annotations.kt @@ -200,4 +200,4 @@ public actual annotation class JsExport @Target(AnnotationTarget.PROPERTY) @SinceKotlin("1.6") @Deprecated("This annotation is a temporal migration assistance and may be removed in the future releases, please consider filing an issue about the case where it is needed") -public annotation class EagerInitialization +public annotation class EagerInitialization \ No newline at end of file