From deb6ca66ea6f9720b0b414dd357dbf2eb73ce376 Mon Sep 17 00:00:00 2001 From: Artem Kobzar Date: Tue, 28 Feb 2023 11:53:28 +0000 Subject: [PATCH] [K/JS] Add undefined type to parameter with default argument that are placed before non-optional parameters ^KT-53180 Fixed --- .../ir/backend/js/export/ExportModel.kt | 1 + .../js/export/ExportModelToTsDeclarations.kt | 32 ++++++++++++------- .../functions-in-exported-file/functions.d.ts | 2 ++ .../functions-in-exported-file/functions.kt | 7 ++++ .../functions__main.js | 8 +++++ .../functions__main.ts | 10 ++++++ .../functions/functions.d.ts | 2 ++ .../typescript-export/functions/functions.kt | 7 ++++ .../functions/functions__main.js | 8 +++++ .../functions/functions__main.ts | 10 ++++++ 10 files changed, 75 insertions(+), 12 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 8809f0a3abe..b248693717f 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 @@ -116,6 +116,7 @@ sealed class ExportedType { object Throwable : Primitive("Error") object Any : Primitive("any") object Unknown : Primitive("unknown") + object Undefined : Primitive("undefined") object Unit : Primitive("void") object Nothing : Primitive("never") object UniqueSymbol : Primitive("unique symbol") 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 cc615af1cdb..20f0c025a14 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 @@ -24,7 +24,7 @@ import org.jetbrains.kotlin.utils.addToStdlib.runIf private const val Nullable = "Nullable" private const val objects = "_objects_" private const val declare = "declare " -private const val declareExorted = "export $declare" +private const val declareExported = "export $declare" private const val NonExistent = "__NonExistent" private const val syntheticObjectNameSeparator = '$' @@ -75,7 +75,7 @@ class ExportModelToTsDeclarations { return joinToString("\n") { it.toTypeScript( indent = moduleKind.indent, - prefix = if (moduleKind == ModuleKind.PLAIN) "" else declareExorted, + prefix = if (moduleKind == ModuleKind.PLAIN) "" else declareExported, esModules = moduleKind == ModuleKind.ES ) } + generateObjectsNamespaceIfNeeded( @@ -116,13 +116,11 @@ class ExportModelToTsDeclarations { } private fun ExportedConstructor.generateTypeScriptString(indent: String): String { - val renderedParameters = parameters.joinToString(", ") { it.toTypeScript(indent) } - return "${visibility.keyword}constructor($renderedParameters);" + return "${visibility.keyword}constructor(${parameters.generateTypeScriptString(indent)});" } private fun ExportedConstructSignature.generateTypeScriptString(indent: String): String { - val renderedParameters = parameters.joinToString(", ") { it.toTypeScript(indent) } - return "new($renderedParameters): ${returnType.toTypeScript(indent)};" + return "new(${parameters.generateTypeScriptString(indent)}): ${returnType.toTypeScript(indent)};" } private fun ExportedProperty.generateTypeScriptString(indent: String, prefix: String, esModules: Boolean = false): String { @@ -181,8 +179,7 @@ class ExportModelToTsDeclarations { else -> "function " } - val renderedParameters = parameters.joinToString(", ") { it.toTypeScript(indent) } - + val renderedParameters = parameters.generateTypeScriptString(indent) val renderedTypeParameters = if (typeParameters.isNotEmpty()) { "<" + typeParameters.joinToString(", ") { it.toTypeScript(indent) } + ">" } else { @@ -394,11 +391,22 @@ class ExportModelToTsDeclarations { return ExportedProperty(name = name, type = type, mutable = false, isMember = true) } - private fun ExportedParameter.toTypeScript(indent: String): String { + private fun List.generateTypeScriptString(indent: String): String { + var couldBeOptional = true + val parameters = foldRight(mutableListOf()) { it, acc -> + if (!it.hasDefaultValue) couldBeOptional = false + acc.apply { add(0, it.toTypeScript(indent, couldBeOptional)) } + } + return parameters.joinToString(", ") + } + + private fun ExportedParameter.toTypeScript(indent: String, couldBeOptional: Boolean): String { val name = sanitizeName(name, withHash = false) - val type = type.toTypeScript(indent) - val questionMark = if (hasDefaultValue) "?" else "" - return "$name$questionMark: $type" + val type = if (hasDefaultValue && !couldBeOptional) { + ExportedType.UnionType(type, ExportedType.Primitive.Undefined) + } else type + val questionMark = if (hasDefaultValue && couldBeOptional) "?" else "" + return "$name$questionMark: ${type.toTypeScript(indent)}" } private fun IrClass.asNestedClassAccess(): String { diff --git a/js/js.translator/testData/typescript-export/functions-in-exported-file/functions.d.ts b/js/js.translator/testData/typescript-export/functions-in-exported-file/functions.d.ts index f59baac8ed6..4eb3bf71dff 100644 --- a/js/js.translator/testData/typescript-export/functions-in-exported-file/functions.d.ts +++ b/js/js.translator/testData/typescript-export/functions-in-exported-file/functions.d.ts @@ -20,5 +20,7 @@ declare namespace JS_TESTS { function inlineFun(x: number, callback: (p0: number) => void): void; function formatList(value: any/* kotlin.collections.List */): string; function createList(): any/* kotlin.collections.List */; + function defaultParametersAtTheBegining(a: string | undefined, b: string): string; + function nonDefaultParameterInBetween(a: string | undefined, b: string, c?: string): string; } } diff --git a/js/js.translator/testData/typescript-export/functions-in-exported-file/functions.kt b/js/js.translator/testData/typescript-export/functions-in-exported-file/functions.kt index 8c1c3b67734..016bbbc7047 100644 --- a/js/js.translator/testData/typescript-export/functions-in-exported-file/functions.kt +++ b/js/js.translator/testData/typescript-export/functions-in-exported-file/functions.kt @@ -72,3 +72,10 @@ fun formatList(value: List<*>): String = value.joinToString(", ") { it.toString( fun createList(): List<*> = listOf(1, 2, 3) + +// KT-53180 + +fun defaultParametersAtTheBegining(a: String = "Default Value", b: String) = "$a and $b" + + +fun nonDefaultParameterInBetween(a: String = "Default A", b: String, c: String = "Default C") = "$a and $b and $c" diff --git a/js/js.translator/testData/typescript-export/functions-in-exported-file/functions__main.js b/js/js.translator/testData/typescript-export/functions-in-exported-file/functions__main.js index c0db7b17635..16efaa54af8 100644 --- a/js/js.translator/testData/typescript-export/functions-in-exported-file/functions__main.js +++ b/js/js.translator/testData/typescript-export/functions-in-exported-file/functions__main.js @@ -25,6 +25,8 @@ var genericWithConstraint = JS_TESTS.foo.genericWithConstraint; var genericWithMultipleConstraints = JS_TESTS.foo.genericWithMultipleConstraints; var formatList = JS_TESTS.foo.formatList; var createList = JS_TESTS.foo.createList; +var defaultParametersAtTheBegining = JS_TESTS.foo.defaultParametersAtTheBegining; +var nonDefaultParametersInBetween = JS_TESTS.foo.nonDefaultParameterInBetween; function assert(condition) { if (!condition) { throw "Assertion failed"; @@ -58,5 +60,11 @@ function box() { inlineFun(10, function (x) { result = x; }); assert(result === 10); assert(formatList(createList()) === "1, 2, 3"); + assert(defaultParametersAtTheBegining("A", "B") == "A and B"); + assert(defaultParametersAtTheBegining(undefined, "B") == "Default Value and B"); + assert(nonDefaultParametersInBetween("A", "B", "C") == "A and B and C"); + assert(nonDefaultParametersInBetween("A", "B") == "A and B and Default C"); + assert(nonDefaultParametersInBetween(undefined, "B", "C") == "Default A and B and C"); + assert(nonDefaultParametersInBetween(undefined, "B") == "Default A and B and Default C"); return "OK"; } diff --git a/js/js.translator/testData/typescript-export/functions-in-exported-file/functions__main.ts b/js/js.translator/testData/typescript-export/functions-in-exported-file/functions__main.ts index 85711f659fc..4b57a6cf770 100644 --- a/js/js.translator/testData/typescript-export/functions-in-exported-file/functions__main.ts +++ b/js/js.translator/testData/typescript-export/functions-in-exported-file/functions__main.ts @@ -13,6 +13,8 @@ import genericWithConstraint = JS_TESTS.foo.genericWithConstraint; import genericWithMultipleConstraints = JS_TESTS.foo.genericWithMultipleConstraints; import formatList = JS_TESTS.foo.formatList; import createList = JS_TESTS.foo.createList; +import defaultParametersAtTheBegining = JS_TESTS.foo.defaultParametersAtTheBegining; +import nonDefaultParametersInBetween = JS_TESTS.foo.nonDefaultParameterInBetween; function assert(condition: boolean) { if (!condition) { throw "Assertion failed"; @@ -57,5 +59,13 @@ function box(): string { assert(formatList(createList()) === "1, 2, 3") + assert(defaultParametersAtTheBegining("A", "B") == "A and B") + assert(defaultParametersAtTheBegining(undefined, "B") == "Default Value and B") + + assert(nonDefaultParametersInBetween("A", "B", "C") == "A and B and C") + assert(nonDefaultParametersInBetween("A", "B") == "A and B and Default C") + assert(nonDefaultParametersInBetween(undefined, "B", "C") == "Default A and B and C") + assert(nonDefaultParametersInBetween(undefined, "B") == "Default A and B and Default C") + return "OK"; } diff --git a/js/js.translator/testData/typescript-export/functions/functions.d.ts b/js/js.translator/testData/typescript-export/functions/functions.d.ts index f59baac8ed6..4eb3bf71dff 100644 --- a/js/js.translator/testData/typescript-export/functions/functions.d.ts +++ b/js/js.translator/testData/typescript-export/functions/functions.d.ts @@ -20,5 +20,7 @@ declare namespace JS_TESTS { function inlineFun(x: number, callback: (p0: number) => void): void; function formatList(value: any/* kotlin.collections.List */): string; function createList(): any/* kotlin.collections.List */; + function defaultParametersAtTheBegining(a: string | undefined, b: string): string; + function nonDefaultParameterInBetween(a: string | undefined, b: string, c?: string): string; } } diff --git a/js/js.translator/testData/typescript-export/functions/functions.kt b/js/js.translator/testData/typescript-export/functions/functions.kt index 02d2be928dc..a1ec11ba868 100644 --- a/js/js.translator/testData/typescript-export/functions/functions.kt +++ b/js/js.translator/testData/typescript-export/functions/functions.kt @@ -68,3 +68,10 @@ fun formatList(value: List<*>): String = value.joinToString(", ") { it.toString( @JsExport fun createList(): List<*> = listOf(1, 2, 3) + +// KT-53180 +@JsExport +fun defaultParametersAtTheBegining(a: String = "Default Value", b: String) = "$a and $b" + +@JsExport +fun nonDefaultParameterInBetween(a: String = "Default A", b: String, c: String = "Default C") = "$a and $b and $c" diff --git a/js/js.translator/testData/typescript-export/functions/functions__main.js b/js/js.translator/testData/typescript-export/functions/functions__main.js index c0db7b17635..16efaa54af8 100644 --- a/js/js.translator/testData/typescript-export/functions/functions__main.js +++ b/js/js.translator/testData/typescript-export/functions/functions__main.js @@ -25,6 +25,8 @@ var genericWithConstraint = JS_TESTS.foo.genericWithConstraint; var genericWithMultipleConstraints = JS_TESTS.foo.genericWithMultipleConstraints; var formatList = JS_TESTS.foo.formatList; var createList = JS_TESTS.foo.createList; +var defaultParametersAtTheBegining = JS_TESTS.foo.defaultParametersAtTheBegining; +var nonDefaultParametersInBetween = JS_TESTS.foo.nonDefaultParameterInBetween; function assert(condition) { if (!condition) { throw "Assertion failed"; @@ -58,5 +60,11 @@ function box() { inlineFun(10, function (x) { result = x; }); assert(result === 10); assert(formatList(createList()) === "1, 2, 3"); + assert(defaultParametersAtTheBegining("A", "B") == "A and B"); + assert(defaultParametersAtTheBegining(undefined, "B") == "Default Value and B"); + assert(nonDefaultParametersInBetween("A", "B", "C") == "A and B and C"); + assert(nonDefaultParametersInBetween("A", "B") == "A and B and Default C"); + assert(nonDefaultParametersInBetween(undefined, "B", "C") == "Default A and B and C"); + assert(nonDefaultParametersInBetween(undefined, "B") == "Default A and B and Default C"); return "OK"; } diff --git a/js/js.translator/testData/typescript-export/functions/functions__main.ts b/js/js.translator/testData/typescript-export/functions/functions__main.ts index 85711f659fc..4b57a6cf770 100644 --- a/js/js.translator/testData/typescript-export/functions/functions__main.ts +++ b/js/js.translator/testData/typescript-export/functions/functions__main.ts @@ -13,6 +13,8 @@ import genericWithConstraint = JS_TESTS.foo.genericWithConstraint; import genericWithMultipleConstraints = JS_TESTS.foo.genericWithMultipleConstraints; import formatList = JS_TESTS.foo.formatList; import createList = JS_TESTS.foo.createList; +import defaultParametersAtTheBegining = JS_TESTS.foo.defaultParametersAtTheBegining; +import nonDefaultParametersInBetween = JS_TESTS.foo.nonDefaultParameterInBetween; function assert(condition: boolean) { if (!condition) { throw "Assertion failed"; @@ -57,5 +59,13 @@ function box(): string { assert(formatList(createList()) === "1, 2, 3") + assert(defaultParametersAtTheBegining("A", "B") == "A and B") + assert(defaultParametersAtTheBegining(undefined, "B") == "Default Value and B") + + assert(nonDefaultParametersInBetween("A", "B", "C") == "A and B and C") + assert(nonDefaultParametersInBetween("A", "B") == "A and B and Default C") + assert(nonDefaultParametersInBetween(undefined, "B", "C") == "Default A and B and C") + assert(nonDefaultParametersInBetween(undefined, "B") == "Default A and B and Default C") + return "OK"; }