Skip to content

Commit

Permalink
Support Kotlin/JS IR and Legacy JS (square#940)
Browse files Browse the repository at this point in the history
This introduces a very ugly workaround to a limitation in the IR compiler
where default parameter values cannot be functions on the invoked object.
https://youtrack.jetbrains.com/issue/KT-45542

It also works around a bug in IR where Long.toString() returns the wrong
value.
https://youtrack.jetbrains.com/issue/KT-39891

Closes: square#910
  • Loading branch information
swankjesse committed May 25, 2021
1 parent 6b77e01 commit 3e3b82b
Show file tree
Hide file tree
Showing 10 changed files with 86 additions and 23 deletions.
1 change: 1 addition & 0 deletions gradle.properties
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
org.gradle.jvmargs='-Dfile.encoding=UTF-8'
android.enableJetifier=true
android.useAndroidX=true
kotlin.js.compiler=both

# Publishing SHA 256 and 512 hashses of maven-metadata is not supported by Sonatype and Nexus.
# See https://github.com/gradle/gradle/issues/11308 and
Expand Down
25 changes: 22 additions & 3 deletions okio/src/commonMain/kotlin/okio/-Util.kt
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package okio

import okio.internal.HEX_DIGIT_CHARS
import kotlin.native.concurrent.SharedImmutable

internal fun checkOffsetAndCount(size: Long, offset: Long, byteCount: Long) {
if (offset or byteCount < 0 || offset > size || size - offset < byteCount) {
Expand Down Expand Up @@ -102,7 +103,7 @@ internal fun Byte.toHexString(): String {
val result = CharArray(2)
result[0] = HEX_DIGIT_CHARS[this shr 4 and 0xf]
result[1] = HEX_DIGIT_CHARS[this and 0xf] // ktlint-disable no-multi-spaces
return String(result)
return result.concatToString()
}

internal fun Int.toHexString(): String {
Expand All @@ -125,7 +126,7 @@ internal fun Int.toHexString(): String {
i++
}

return String(result, i, result.size - i)
return result.concatToString(i, result.size)
}

internal fun Long.toHexString(): String {
Expand Down Expand Up @@ -156,5 +157,23 @@ internal fun Long.toHexString(): String {
i++
}

return String(result, i, result.size - i)
return result.concatToString(i, result.size)
}

// Work around a problem where Kotlin/JS IR can't handle default parameters on expect functions
// that depend on the receiver. We use well-known, otherwise-impossible values here and must check
// for them in the receiving function, then swap in the true default value.
// https://youtrack.jetbrains.com/issue/KT-45542

@SharedImmutable
internal val DEFAULT__new_UnsafeCursor = Buffer.UnsafeCursor()
internal fun resolveDefaultParameter(unsafeCursor: Buffer.UnsafeCursor): Buffer.UnsafeCursor {
if (unsafeCursor === DEFAULT__new_UnsafeCursor) return Buffer.UnsafeCursor()
return unsafeCursor
}

internal val DEFAULT__ByteString_size = -1234567890
internal fun ByteString.resolveDefaultParameter(position: Int): Int {
if (position == DEFAULT__ByteString_size) return size
return position
}
4 changes: 2 additions & 2 deletions okio/src/commonMain/kotlin/okio/Buffer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -142,9 +142,9 @@ expect class Buffer() : BufferedSource, BufferedSink {
/** Returns an immutable copy of the first `byteCount` bytes of this buffer as a byte string. */
fun snapshot(byteCount: Int): ByteString

fun readUnsafe(unsafeCursor: UnsafeCursor = UnsafeCursor()): UnsafeCursor
fun readUnsafe(unsafeCursor: UnsafeCursor = DEFAULT__new_UnsafeCursor): UnsafeCursor

fun readAndWriteUnsafe(unsafeCursor: UnsafeCursor = UnsafeCursor()): UnsafeCursor
fun readAndWriteUnsafe(unsafeCursor: UnsafeCursor = DEFAULT__new_UnsafeCursor): UnsafeCursor

/**
* A handle to the underlying data in a buffer. This handle is unsafe because it does not enforce
Expand Down
6 changes: 3 additions & 3 deletions okio/src/commonMain/kotlin/okio/ByteString.kt
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ internal constructor(data: ByteArray) : Comparable<ByteString> {
* `beginIndex` and ends at the specified `endIndex`. Returns this byte string if `beginIndex` is
* 0 and `endIndex` is the length of this byte string.
*/
fun substring(beginIndex: Int = 0, endIndex: Int = size): ByteString
fun substring(beginIndex: Int = 0, endIndex: Int = DEFAULT__ByteString_size): ByteString

/**
* Returns a byte string equal to this byte string, but with the bytes 'a' through 'z' replaced
Expand Down Expand Up @@ -147,9 +147,9 @@ internal constructor(data: ByteArray) : Comparable<ByteString> {
@JvmOverloads
fun indexOf(other: ByteArray, fromIndex: Int = 0): Int

fun lastIndexOf(other: ByteString, fromIndex: Int = size): Int
fun lastIndexOf(other: ByteString, fromIndex: Int = DEFAULT__ByteString_size): Int

fun lastIndexOf(other: ByteArray, fromIndex: Int = size): Int
fun lastIndexOf(other: ByteArray, fromIndex: Int = DEFAULT__ByteString_size): Int

override fun equals(other: Any?): Boolean

Expand Down
3 changes: 3 additions & 0 deletions okio/src/commonMain/kotlin/okio/internal/-Buffer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import okio.and
import okio.asUtf8ToByteArray
import okio.checkOffsetAndCount
import okio.minOf
import okio.resolveDefaultParameter
import okio.toHexString
import kotlin.native.concurrent.SharedImmutable

Expand Down Expand Up @@ -1509,6 +1510,7 @@ internal inline fun Buffer.commonSnapshot(byteCount: Int): ByteString {
}

internal fun Buffer.commonReadUnsafe(unsafeCursor: UnsafeCursor): UnsafeCursor {
val unsafeCursor = resolveDefaultParameter(unsafeCursor)
check(unsafeCursor.buffer == null) { "already attached to a buffer" }

unsafeCursor.buffer = this
Expand All @@ -1517,6 +1519,7 @@ internal fun Buffer.commonReadUnsafe(unsafeCursor: UnsafeCursor): UnsafeCursor {
}

internal fun Buffer.commonReadAndWriteUnsafe(unsafeCursor: UnsafeCursor): UnsafeCursor {
val unsafeCursor = resolveDefaultParameter(unsafeCursor)
check(unsafeCursor.buffer == null) { "already attached to a buffer" }

unsafeCursor.buffer = this
Expand Down
5 changes: 4 additions & 1 deletion okio/src/commonMain/kotlin/okio/internal/-ByteString.kt
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import okio.decodeBase64ToArray
import okio.encodeBase64
import okio.isIsoControl
import okio.processUtf8CodePoints
import okio.resolveDefaultParameter
import okio.shr
import okio.toUtf8String
import kotlin.native.concurrent.SharedImmutable
Expand Down Expand Up @@ -64,7 +65,7 @@ internal inline fun ByteString.commonHex(): String {
result[c++] = HEX_DIGIT_CHARS[b shr 4 and 0xf]
result[c++] = HEX_DIGIT_CHARS[b and 0xf] // ktlint-disable no-multi-spaces
}
return String(result)
return result.concatToString()
}

@Suppress("NOTHING_TO_INLINE")
Expand Down Expand Up @@ -125,6 +126,7 @@ internal inline fun ByteString.commonToAsciiUppercase(): ByteString {

@Suppress("NOTHING_TO_INLINE")
internal inline fun ByteString.commonSubstring(beginIndex: Int, endIndex: Int): ByteString {
val endIndex = resolveDefaultParameter(endIndex)
require(beginIndex >= 0) { "beginIndex < 0" }
require(endIndex <= data.size) { "endIndex > length(${data.size})" }

Expand Down Expand Up @@ -206,6 +208,7 @@ internal inline fun ByteString.commonLastIndexOf(

@Suppress("NOTHING_TO_INLINE")
internal inline fun ByteString.commonLastIndexOf(other: ByteArray, fromIndex: Int): Int {
val fromIndex = resolveDefaultParameter(fromIndex)
val limit = data.size - other.size
for (i in minOf(fromIndex, limit) downTo 0) {
if (arrayRangeEquals(data, i, other, 0, other.size)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import okio.Segment
import okio.SegmentedByteString
import okio.arrayRangeEquals
import okio.checkOffsetAndCount
import okio.resolveDefaultParameter

internal fun IntArray.binarySearch(value: Int, fromIndex: Int, toIndex: Int): Int {
var left = fromIndex
Expand Down Expand Up @@ -97,6 +98,7 @@ private inline fun SegmentedByteString.forEachSegment(
// have to call these functions. Remove all this nonsense when expect class allow actual code.

internal inline fun SegmentedByteString.commonSubstring(beginIndex: Int, endIndex: Int): ByteString {
val endIndex = resolveDefaultParameter(endIndex)
require(beginIndex >= 0) { "beginIndex=$beginIndex < 0" }
require(endIndex <= size) { "endIndex=$endIndex > length($size)" }

Expand Down
2 changes: 1 addition & 1 deletion okio/src/commonMain/kotlin/okio/internal/-Utf8.kt
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ fun ByteArray.commonToUtf8String(beginIndex: Int = 0, endIndex: Int = size): Str
chars[length++] = c
}

return String(chars, 0, length)
return chars.concatToString(0, length)
}

fun String.commonAsUtf8ToByteArray(): ByteArray {
Expand Down
57 changes: 45 additions & 12 deletions okio/src/commonTest/kotlin/okio/AbstractBufferedSinkTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ package okio

import okio.ByteString.Companion.decodeHex
import okio.ByteString.Companion.encodeUtf8
import kotlin.math.pow
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
Expand Down Expand Up @@ -222,21 +221,55 @@ abstract class AbstractBufferedSinkTest internal constructor(
assertEquals('a', data.readByte().toChar())
}

/**
* This test hard codes the results of Long.toString() because that function rounds large values
* when using Kotlin/JS IR. https://youtrack.jetbrains.com/issue/KT-39891
*/
@Test fun longDecimalString() {
assertLongDecimalString(0)
assertLongDecimalString(Long.MIN_VALUE)
assertLongDecimalString(Long.MAX_VALUE)

for (i in 1..19) {
val value = 10.0.pow(i).toLong()
assertLongDecimalString(value - 1)
assertLongDecimalString(value)
}
assertLongDecimalString("0", 0)
assertLongDecimalString("-9223372036854775808", Long.MIN_VALUE)
assertLongDecimalString("9223372036854775807", Long.MAX_VALUE)
assertLongDecimalString("9", 9L)
assertLongDecimalString("99", 99L)
assertLongDecimalString("999", 999L)
assertLongDecimalString("9999", 9999L)
assertLongDecimalString("99999", 99999L)
assertLongDecimalString("999999", 999999L)
assertLongDecimalString("9999999", 9999999L)
assertLongDecimalString("99999999", 99999999L)
assertLongDecimalString("999999999", 999999999L)
assertLongDecimalString("9999999999", 9999999999L)
assertLongDecimalString("99999999999", 99999999999L)
assertLongDecimalString("999999999999", 999999999999L)
assertLongDecimalString("9999999999999", 9999999999999L)
assertLongDecimalString("99999999999999", 99999999999999L)
assertLongDecimalString("999999999999999", 999999999999999L)
assertLongDecimalString("9999999999999999", 9999999999999999L)
assertLongDecimalString("99999999999999999", 99999999999999999L)
assertLongDecimalString("999999999999999999", 999999999999999999L)
assertLongDecimalString("10", 10L)
assertLongDecimalString("100", 100L)
assertLongDecimalString("1000", 1000L)
assertLongDecimalString("10000", 10000L)
assertLongDecimalString("100000", 100000L)
assertLongDecimalString("1000000", 1000000L)
assertLongDecimalString("10000000", 10000000L)
assertLongDecimalString("100000000", 100000000L)
assertLongDecimalString("1000000000", 1000000000L)
assertLongDecimalString("10000000000", 10000000000L)
assertLongDecimalString("100000000000", 100000000000L)
assertLongDecimalString("1000000000000", 1000000000000L)
assertLongDecimalString("10000000000000", 10000000000000L)
assertLongDecimalString("100000000000000", 100000000000000L)
assertLongDecimalString("1000000000000000", 1000000000000000L)
assertLongDecimalString("10000000000000000", 10000000000000000L)
assertLongDecimalString("100000000000000000", 100000000000000000L)
assertLongDecimalString("1000000000000000000", 1000000000000000000L)
}

private fun assertLongDecimalString(value: Long) {
private fun assertLongDecimalString(string: String, value: Long) {
sink.writeDecimalLong(value).writeUtf8("zzz").flush()
val expected = "${value}zzz"
val expected = "${string}zzz"
val actual = data.readUtf8()
assertEquals(expected, actual, "$value expected $expected but was $actual")
}
Expand Down
4 changes: 3 additions & 1 deletion okio/src/commonTest/kotlin/okio/AbstractFileSystemTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,9 @@ abstract class AbstractFileSystemTest(
val cwdString = cwd.toString()
assertTrue(cwdString) {
cwdString.endsWith("okio${Path.DIRECTORY_SEPARATOR}okio") ||
cwdString.endsWith("${Path.DIRECTORY_SEPARATOR}okio-parent-okio-test") || // JS
cwdString.endsWith("${Path.DIRECTORY_SEPARATOR}okio-parent-okio-jsLegacy-test") ||
cwdString.endsWith("${Path.DIRECTORY_SEPARATOR}okio-parent-okio-js-ir-test") ||
cwdString.endsWith("${Path.DIRECTORY_SEPARATOR}okio-parent-okio-jsIr-test") ||
cwdString.contains("/CoreSimulator/Devices/") || // iOS simulator.
cwdString == "/" // Android emulator.
}
Expand Down

0 comments on commit 3e3b82b

Please sign in to comment.