From 35acade0312b0817ce177d3fa2aaaa9b04a3bc64 Mon Sep 17 00:00:00 2001 From: Artem Kobzar Date: Fri, 1 Mar 2024 13:20:33 +0000 Subject: [PATCH] [K/JS] Fix problem with saving of parameter's default values after overriding ^KT-63907 Fixed --- .../backend/js/export/ExportModelGenerator.kt | 20 +++++++++++++++---- .../interfaces.d.ts | 13 ++++++++++++ .../interfaces-in-exported-file/interfaces.kt | 13 +++++++++++- .../interfaces__main.ts | 7 +++++++ .../interfaces/interfaces.d.ts | 13 ++++++++++++ .../interfaces/interfaces.kt | 13 ++++++++++++ .../interfaces/interfaces__main.ts | 7 +++++++ 7 files changed, 81 insertions(+), 5 deletions(-) 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 eeee825ebbe..3e26d9d6c51 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 @@ -85,6 +85,7 @@ class ExportModelGenerator(val context: JsIrBackendContext, val generateNamespac is Exportability.Prohibited -> ErrorDeclaration(exportability.reason) is Exportability.Allowed -> { val parent = function.parent + val realOverrideTarget = function.realOverrideTarget ExportedFunction( function.getExportedIdentifier(), returnType = exportType(function.returnType), @@ -96,7 +97,13 @@ class ExportModelGenerator(val context: JsIrBackendContext, val generateNamespac ir = function, parameters = (listOfNotNull(function.extensionReceiverParameter) + function.valueParameters) .filter { it.shouldBeExported() } - .memoryOptimizedMap { exportParameter(it) }, + .memoryOptimizedMapIndexed { i, it -> + exportParameter( + it, + it.hasDefaultValue + || realOverrideTarget.valueParameters.getOrNull(i)?.hasDefaultValue == true + ) + } ) } } @@ -106,12 +113,14 @@ class ExportModelGenerator(val context: JsIrBackendContext, val generateNamespac if (!constructor.isPrimary) return null val allValueParameters = listOfNotNull(constructor.extensionReceiverParameter) + constructor.valueParameters return ExportedConstructor( - parameters = allValueParameters.filterNot { it.isBoxParameter }.memoryOptimizedMap { exportParameter(it) }, + parameters = allValueParameters + .filterNot { it.isBoxParameter } + .memoryOptimizedMap { exportParameter(it, it.hasDefaultValue) }, visibility = constructor.visibility.toExportedVisibility() ) } - private fun exportParameter(parameter: IrValueParameter): ExportedParameter { + private fun exportParameter(parameter: IrValueParameter, hasDefaultValue: Boolean): ExportedParameter { // Parameter names do not matter in d.ts files. They can be renamed as we like var parameterName = sanitizeName(parameter.name.asString(), withHash = false) if (parameterName in allReservedWords) @@ -120,10 +129,13 @@ class ExportModelGenerator(val context: JsIrBackendContext, val generateNamespac return ExportedParameter( parameterName, exportType(parameter.type), - parameter.origin == JsLoweredDeclarationOrigin.JS_SHADOWED_DEFAULT_PARAMETER + hasDefaultValue ) } + private val IrValueParameter.hasDefaultValue: Boolean + get() = origin == JsLoweredDeclarationOrigin.JS_SHADOWED_DEFAULT_PARAMETER + private fun exportProperty(property: IrProperty): ExportedDeclaration? { for (accessor in listOfNotNull(property.getter, property.setter)) { // TODO: Report a frontend error diff --git a/js/js.translator/testData/typescript-export/interfaces-in-exported-file/interfaces.d.ts b/js/js.translator/testData/typescript-export/interfaces-in-exported-file/interfaces.d.ts index c814c106b18..a99d7d086f5 100644 --- a/js/js.translator/testData/typescript-export/interfaces-in-exported-file/interfaces.d.ts +++ b/js/js.translator/testData/typescript-export/interfaces-in-exported-file/interfaces.d.ts @@ -53,5 +53,18 @@ declare namespace JS_TESTS { readonly "foo.ExportedChildInterface": unique symbol; }; } + interface InterfaceWithDefaultArguments { + foo(x?: number): number; + bar(x?: number): number; + readonly __doNotUseOrImplementIt: { + readonly "foo.InterfaceWithDefaultArguments": unique symbol; + }; + } + class ImplementorOfInterfaceWithDefaultArguments implements foo.InterfaceWithDefaultArguments { + constructor(); + bar(x?: number): number; + foo(x?: number): number; + readonly __doNotUseOrImplementIt: foo.InterfaceWithDefaultArguments["__doNotUseOrImplementIt"]; + } } } diff --git a/js/js.translator/testData/typescript-export/interfaces-in-exported-file/interfaces.kt b/js/js.translator/testData/typescript-export/interfaces-in-exported-file/interfaces.kt index 333afe7985b..5646f723558 100644 --- a/js/js.translator/testData/typescript-export/interfaces-in-exported-file/interfaces.kt +++ b/js/js.translator/testData/typescript-export/interfaces-in-exported-file/interfaces.kt @@ -68,9 +68,20 @@ interface InterfaceWithCompanion { } +// KT-64708 external interface ExportedParentInterface - interface ExportedChildInterface : ExportedParentInterface { fun bar() +} + +// KT-63907 +interface InterfaceWithDefaultArguments { + fun foo(x: Int = 0) = x + fun bar(x: Int = 0) = x +} + + +class ImplementorOfInterfaceWithDefaultArguments : InterfaceWithDefaultArguments { + override fun bar(x: Int) = x + 1 } \ No newline at end of file diff --git a/js/js.translator/testData/typescript-export/interfaces-in-exported-file/interfaces__main.ts b/js/js.translator/testData/typescript-export/interfaces-in-exported-file/interfaces__main.ts index be559a4b43b..b7bd7e7239a 100644 --- a/js/js.translator/testData/typescript-export/interfaces-in-exported-file/interfaces__main.ts +++ b/js/js.translator/testData/typescript-export/interfaces-in-exported-file/interfaces__main.ts @@ -3,6 +3,7 @@ import ChildTestInterfaceImpl = JS_TESTS.foo.ChildTestInterfaceImpl; import processInterface = JS_TESTS.foo.processInterface; import processOptionalInterface = JS_TESTS.foo.processOptionalInterface; import WithTheCompanion = JS_TESTS.foo.WithTheCompanion; +import ImplementorOfInterfaceWithDefaultArguments = JS_TESTS.foo.ImplementorOfInterfaceWithDefaultArguments; function assert(condition: boolean) { if (!condition) { @@ -23,5 +24,11 @@ function box(): string { assert(WithTheCompanion.companionFunction() == "FUNCTION") + const instance = new ImplementorOfInterfaceWithDefaultArguments() + assert(instance.foo() === 0); + assert(instance.foo(2) === 2); + assert(instance.bar() === 1); + assert(instance.bar(2) === 3); + return "OK"; } \ No newline at end of file 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 c814c106b18..a99d7d086f5 100644 --- a/js/js.translator/testData/typescript-export/interfaces/interfaces.d.ts +++ b/js/js.translator/testData/typescript-export/interfaces/interfaces.d.ts @@ -53,5 +53,18 @@ declare namespace JS_TESTS { readonly "foo.ExportedChildInterface": unique symbol; }; } + interface InterfaceWithDefaultArguments { + foo(x?: number): number; + bar(x?: number): number; + readonly __doNotUseOrImplementIt: { + readonly "foo.InterfaceWithDefaultArguments": unique symbol; + }; + } + class ImplementorOfInterfaceWithDefaultArguments implements foo.InterfaceWithDefaultArguments { + constructor(); + bar(x?: number): number; + foo(x?: number): number; + readonly __doNotUseOrImplementIt: foo.InterfaceWithDefaultArguments["__doNotUseOrImplementIt"]; + } } } diff --git a/js/js.translator/testData/typescript-export/interfaces/interfaces.kt b/js/js.translator/testData/typescript-export/interfaces/interfaces.kt index 504a2504d58..f8b1706255a 100644 --- a/js/js.translator/testData/typescript-export/interfaces/interfaces.kt +++ b/js/js.translator/testData/typescript-export/interfaces/interfaces.kt @@ -63,10 +63,23 @@ interface InterfaceWithCompanion { } } +// KT-64708 @JsExport external interface ExportedParentInterface @JsExport interface ExportedChildInterface : ExportedParentInterface { fun bar() +} + +// KT-63907 +@JsExport +interface InterfaceWithDefaultArguments { + fun foo(x: Int = 0) = x + fun bar(x: Int = 0) = x +} + +@JsExport +class ImplementorOfInterfaceWithDefaultArguments : InterfaceWithDefaultArguments { + override fun bar(x: Int) = x + 1 } \ No newline at end of file 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 be559a4b43b..b7bd7e7239a 100644 --- a/js/js.translator/testData/typescript-export/interfaces/interfaces__main.ts +++ b/js/js.translator/testData/typescript-export/interfaces/interfaces__main.ts @@ -3,6 +3,7 @@ import ChildTestInterfaceImpl = JS_TESTS.foo.ChildTestInterfaceImpl; import processInterface = JS_TESTS.foo.processInterface; import processOptionalInterface = JS_TESTS.foo.processOptionalInterface; import WithTheCompanion = JS_TESTS.foo.WithTheCompanion; +import ImplementorOfInterfaceWithDefaultArguments = JS_TESTS.foo.ImplementorOfInterfaceWithDefaultArguments; function assert(condition: boolean) { if (!condition) { @@ -23,5 +24,11 @@ function box(): string { assert(WithTheCompanion.companionFunction() == "FUNCTION") + const instance = new ImplementorOfInterfaceWithDefaultArguments() + assert(instance.foo() === 0); + assert(instance.foo(2) === 2); + assert(instance.bar() === 1); + assert(instance.bar(2) === 3); + return "OK"; } \ No newline at end of file