diff --git a/kotlin-native/runtime/src/main/cpp/KString.cpp b/kotlin-native/runtime/src/main/cpp/KString.cpp index 51813a00c63..c0a55541b02 100644 --- a/kotlin-native/runtime/src/main/cpp/KString.cpp +++ b/kotlin-native/runtime/src/main/cpp/KString.cpp @@ -207,12 +207,12 @@ OBJ_GETTER(Kotlin_String_unsafeStringFromCharArray, KConstRef thiz, KInt start, RETURN_OBJ(result->obj()); } -OBJ_GETTER(Kotlin_String_toCharArray, KString string, KInt start, KInt size) { - ArrayHeader* result = AllocArrayInstance(theCharArrayTypeInfo, size, OBJ_RESULT)->array(); - memcpy(CharArrayAddressOfElementAt(result, 0), +OBJ_GETTER(Kotlin_String_toCharArray, KString string, KRef destination, KInt destinationOffset, KInt start, KInt size) { + ArrayHeader* destinationArray = destination->array(); + memcpy(CharArrayAddressOfElementAt(destinationArray, destinationOffset), CharArrayAddressOfElementAt(string, start), size * sizeof(KChar)); - RETURN_OBJ(result->obj()); + RETURN_OBJ(destinationArray->obj()); } OBJ_GETTER(Kotlin_String_subSequence, KString thiz, KInt startIndex, KInt endIndex) { diff --git a/kotlin-native/runtime/src/main/kotlin/kotlin/text/Strings.kt b/kotlin-native/runtime/src/main/kotlin/kotlin/text/Strings.kt index fef4fb69485..e64e9cb96fd 100644 --- a/kotlin-native/runtime/src/main/kotlin/kotlin/text/Strings.kt +++ b/kotlin-native/runtime/src/main/kotlin/kotlin/text/Strings.kt @@ -256,10 +256,10 @@ public actual fun String.lowercase(): String = lowercaseImpl() /** * Returns a [CharArray] containing characters of this string. */ -public actual fun String.toCharArray(): CharArray = toCharArray(this, 0, length) +public actual fun String.toCharArray(): CharArray = toCharArray(this, CharArray(length), 0, 0, length) @GCUnsafeCall("Kotlin_String_toCharArray") -private external fun toCharArray(string: String, start: Int, size: Int): CharArray +private external fun toCharArray(string: String, destination: CharArray, destinationOffset: Int, start: Int, size: Int): CharArray /** * Returns a copy of this string having its first letter titlecased using the rules of the default locale, @@ -377,7 +377,35 @@ internal external fun unsafeStringFromCharArray(array: CharArray, start: Int, si @Suppress("ACTUAL_FUNCTION_WITH_DEFAULT_ARGUMENTS") public actual fun String.toCharArray(startIndex: Int = 0, endIndex: Int = this.length): CharArray { AbstractList.checkBoundsIndexes(startIndex, endIndex, length) - return toCharArray(this, startIndex, endIndex - startIndex) + val rangeSize = endIndex - startIndex + return toCharArray(this, CharArray(rangeSize), 0, startIndex, rangeSize) +} + +/** + * Copies characters from this string into the [destination] character array and returns that array. + * + * @param destination the array to copy to. + * @param destinationOffset the position in the array to copy to. + * @param startIndex the start offset (inclusive) of the substring to copy. + * @param endIndex the end offset (exclusive) of the substring to copy. + * + * @throws IndexOutOfBoundsException or [IllegalArgumentException] when [startIndex] or [endIndex] is out of range of this string builder indices or when `startIndex > endIndex`. + * @throws IndexOutOfBoundsException when the subrange doesn't fit into the [destination] array starting at the specified [destinationOffset], + * or when that index is out of the [destination] array indices range. + */ +@ExperimentalStdlibApi +@SinceKotlin("1.9") +@Suppress("ACTUAL_FUNCTION_WITH_DEFAULT_ARGUMENTS") +public actual fun String.toCharArray( + destination: CharArray, + destinationOffset: Int = 0, + startIndex: Int = 0, + endIndex: Int = length +): CharArray { + AbstractList.checkBoundsIndexes(startIndex, endIndex, length) + val rangeSize = endIndex - startIndex + AbstractList.checkBoundsIndexes(destinationOffset, destinationOffset + rangeSize, destination.size) + return toCharArray(this, destination, destinationOffset, startIndex, rangeSize) } /** diff --git a/libraries/stdlib/api/js/kotlin.text.kt b/libraries/stdlib/api/js/kotlin.text.kt index 99df0794897..9ee24f90d55 100644 --- a/libraries/stdlib/api/js/kotlin.text.kt +++ b/libraries/stdlib/api/js/kotlin.text.kt @@ -1052,6 +1052,10 @@ public fun kotlin.String.toByteOrNull(radix: kotlin.Int): kotlin.Byte? @kotlin.WasExperimental(markerClass = {kotlin.ExperimentalStdlibApi::class}) public fun kotlin.String.toCharArray(): kotlin.CharArray +@kotlin.ExperimentalStdlibApi +@kotlin.SinceKotlin(version = "1.9") +public fun kotlin.String.toCharArray(destination: kotlin.CharArray, destinationOffset: kotlin.Int = ..., startIndex: kotlin.Int = ..., endIndex: kotlin.Int = ...): kotlin.CharArray + @kotlin.SinceKotlin(version = "1.4") @kotlin.WasExperimental(markerClass = {kotlin.ExperimentalStdlibApi::class}) public fun kotlin.String.toCharArray(startIndex: kotlin.Int = ..., endIndex: kotlin.Int = ...): kotlin.CharArray diff --git a/libraries/stdlib/common/src/kotlin/TextH.kt b/libraries/stdlib/common/src/kotlin/TextH.kt index 644fd38a42e..7f469d0725c 100644 --- a/libraries/stdlib/common/src/kotlin/TextH.kt +++ b/libraries/stdlib/common/src/kotlin/TextH.kt @@ -169,6 +169,27 @@ public expect fun String.toCharArray(): CharArray @WasExperimental(ExperimentalStdlibApi::class) public expect fun String.toCharArray(startIndex: Int = 0, endIndex: Int = this.length): CharArray +/** + * Copies characters from this string into the [destination] character array and returns that array. + * + * @param destination the array to copy to. + * @param destinationOffset the position in the array to copy to. + * @param startIndex the start offset (inclusive) of the substring to copy. + * @param endIndex the end offset (exclusive) of the substring to copy. + * + * @throws IndexOutOfBoundsException or [IllegalArgumentException] when [startIndex] or [endIndex] is out of range of this string builder indices or when `startIndex > endIndex`. + * @throws IndexOutOfBoundsException when the subrange doesn't fit into the [destination] array starting at the specified [destinationOffset], + * or when that index is out of the [destination] array indices range. + */ +@ExperimentalStdlibApi +@SinceKotlin("1.9") +public expect fun String.toCharArray( + destination: CharArray, + destinationOffset: Int = 0, + startIndex: Int = 0, + endIndex: Int = length +): CharArray + /** * Decodes a string from the bytes in UTF-8 encoding in this array. * diff --git a/libraries/stdlib/js/src/kotlin/text/stringJs.kt b/libraries/stdlib/js/src/kotlin/text/stringJs.kt index 2eee5c537fb..e9a68e408ac 100644 --- a/libraries/stdlib/js/src/kotlin/text/stringJs.kt +++ b/libraries/stdlib/js/src/kotlin/text/stringJs.kt @@ -100,6 +100,36 @@ public actual fun String.toCharArray(startIndex: Int = 0, endIndex: Int = this.l return CharArray(endIndex - startIndex) { get(startIndex + it) } } +/** + * Copies characters from this string into the [destination] character array and returns that array. + * + * @param destination the array to copy to. + * @param destinationOffset the position in the array to copy to. + * @param startIndex the start offset (inclusive) of the substring to copy. + * @param endIndex the end offset (exclusive) of the substring to copy. + * + * @throws IndexOutOfBoundsException or [IllegalArgumentException] when [startIndex] or [endIndex] is out of range of this string builder indices or when `startIndex > endIndex`. + * @throws IndexOutOfBoundsException when the subrange doesn't fit into the [destination] array starting at the specified [destinationOffset], + * or when that index is out of the [destination] array indices range. + */ +@ExperimentalStdlibApi +@SinceKotlin("1.9") +@Suppress("ACTUAL_FUNCTION_WITH_DEFAULT_ARGUMENTS") +public actual fun String.toCharArray( + destination: CharArray, + destinationOffset: Int = 0, + startIndex: Int = 0, + endIndex: Int = length +): CharArray { + AbstractList.checkBoundsIndexes(startIndex, endIndex, length) + AbstractList.checkBoundsIndexes(destinationOffset, destinationOffset + endIndex - startIndex, destination.size) + var destIndex = destinationOffset + for (i in startIndex until endIndex) { + destination[destIndex++] = this[i] + } + return destination +} + /** * Decodes a string from the bytes in UTF-8 encoding in this array. * diff --git a/libraries/stdlib/jvm/src/kotlin/text/StringsJVM.kt b/libraries/stdlib/jvm/src/kotlin/text/StringsJVM.kt index 5be725b3076..27f28594962 100644 --- a/libraries/stdlib/jvm/src/kotlin/text/StringsJVM.kt +++ b/libraries/stdlib/jvm/src/kotlin/text/StringsJVM.kt @@ -314,9 +314,14 @@ public actual inline fun String.toCharArray(): CharArray = (this as java.lang.St * @param destinationOffset the position in the array to copy to. * @param startIndex the start offset (inclusive) of the substring to copy. * @param endIndex the end offset (exclusive) of the substring to copy. + * + * @throws IndexOutOfBoundsException or [IllegalArgumentException] when [startIndex] or [endIndex] is out of range of this string builder indices or when `startIndex > endIndex`. + * @throws IndexOutOfBoundsException when the subrange doesn't fit into the [destination] array starting at the specified [destinationOffset], + * or when that index is out of the [destination] array indices range. */ +@Suppress("ACTUAL_FUNCTION_WITH_DEFAULT_ARGUMENTS") @kotlin.internal.InlineOnly -public inline fun String.toCharArray( +public actual inline fun String.toCharArray( destination: CharArray, destinationOffset: Int = 0, startIndex: Int = 0, diff --git a/libraries/stdlib/test/text/StringTest.kt b/libraries/stdlib/test/text/StringTest.kt index c985d5ae23b..96aaa539eba 100644 --- a/libraries/stdlib/test/text/StringTest.kt +++ b/libraries/stdlib/test/text/StringTest.kt @@ -115,24 +115,25 @@ class StringTest { @Test fun toCharArray() { val s = "hello" + val destination = CharArray(5) { '.' } assertArrayContentEquals(charArrayOf('h', 'e', 'l', 'l', 'o'), s.toCharArray()) assertArrayContentEquals(charArrayOf('e', 'l'), s.toCharArray(1, 3)) + assertSame(destination, s.toCharArray(destination, 2, 1, 3)) + assertArrayContentEquals(charArrayOf('.', '.', 'e', 'l', '.'), destination) assertFailsWith { s.toCharArray(-1) } assertFailsWith { s.toCharArray(0, 6) } assertFailsWith { s.toCharArray(3, 1) } + assertFailsWith { s.toCharArray(destination, -1, 1, 3) } + assertFailsWith { s.toCharArray(destination, 4, 1, 3) } // Array modifications must not affect original string val a = s.toCharArray() - for (i in a.indices) { - a[i] = ' ' - } - assertContentEquals(charArrayOf(' ', ' ', ' ', ' ', ' '), a) + a.fill(' ') val a13 = s.toCharArray(1, 3) - for (i in a13.indices) { - a13[i] = ' ' - } - assertContentEquals(charArrayOf(' ', ' '), a13) + a13.fill(' ') + assertSame(destination, s.toCharArray(destination)) + destination.fill(' ') assertEquals("hello", s) } diff --git a/libraries/stdlib/wasm/src/kotlin/text/StringsWasm.kt b/libraries/stdlib/wasm/src/kotlin/text/StringsWasm.kt index 753439f502d..af94773087b 100644 --- a/libraries/stdlib/wasm/src/kotlin/text/StringsWasm.kt +++ b/libraries/stdlib/wasm/src/kotlin/text/StringsWasm.kt @@ -145,6 +145,34 @@ public actual fun String.toCharArray(startIndex: Int = 0, endIndex: Int = this.l return newArray } +/** + * Copies characters from this string into the [destination] character array and returns that array. + * + * @param destination the array to copy to. + * @param destinationOffset the position in the array to copy to. + * @param startIndex the start offset (inclusive) of the substring to copy. + * @param endIndex the end offset (exclusive) of the substring to copy. + * + * @throws IndexOutOfBoundsException or [IllegalArgumentException] when [startIndex] or [endIndex] is out of range of this string builder indices or when `startIndex > endIndex`. + * @throws IndexOutOfBoundsException when the subrange doesn't fit into the [destination] array starting at the specified [destinationOffset], + * or when that index is out of the [destination] array indices range. + */ +@ExperimentalStdlibApi +@SinceKotlin("1.9") +@Suppress("ACTUAL_FUNCTION_WITH_DEFAULT_ARGUMENTS") +public actual fun String.toCharArray( + destination: CharArray, + destinationOffset: Int = 0, + startIndex: Int = 0, + endIndex: Int = length +): CharArray { + AbstractList.checkBoundsIndexes(startIndex, endIndex, length) + val rangeSize = endIndex - startIndex + AbstractList.checkBoundsIndexes(destinationOffset, destinationOffset + rangeSize, destination.size) + copyWasmArray(this.chars, destination.storage, startIndex, destinationOffset, rangeSize) + return destination +} + /** * Decodes a string from the bytes in UTF-8 encoding in this array. *