[Wasm] Imporove external interface support
* Support boxing/unboxing when casting to Any * Support ===, equals, hashCode, toString * Support adapting String in interop boundary
This commit is contained in:
@@ -0,0 +1,231 @@
|
||||
// WITH_RUNTIME
|
||||
// FILE: externals.js
|
||||
|
||||
const primitives1 = [3.14, "Test string 1", true, Symbol("symbol"), 131283889534859707199254740992n];
|
||||
const primitives2 = [3.15, "Test string 2", false, Symbol("symbol"), 231283889534859707199254740992n];
|
||||
|
||||
function getPrimitive1(n) {
|
||||
return primitives1[n];
|
||||
}
|
||||
|
||||
function getPrimitive2(n) {
|
||||
return primitives2[n];
|
||||
}
|
||||
|
||||
function testPrimitive(obj, n) {
|
||||
return obj === primitives1[n];
|
||||
}
|
||||
|
||||
function getNewObject() {
|
||||
return { value: 123 };
|
||||
}
|
||||
|
||||
function testObject(obj) {
|
||||
return obj.value === 123;
|
||||
}
|
||||
|
||||
function getNewArray() {
|
||||
return [1, 2, 3];
|
||||
}
|
||||
|
||||
function testArray(obj) {
|
||||
return Array.isArray(obj) && obj[0] === 1 && obj[1] === 2 && obj [2] === 3;
|
||||
}
|
||||
|
||||
function roundTrip(x) { return x; }
|
||||
|
||||
// FILE: externals.kt
|
||||
external interface Obj
|
||||
external interface SubObj : Obj
|
||||
|
||||
external fun getPrimitive1(n: Int): Obj
|
||||
external fun getPrimitive2(n: Int): Obj
|
||||
external fun testPrimitive(obj: Obj, n: Int): Boolean
|
||||
|
||||
external fun getNewObject(): Obj
|
||||
external fun testObject(obj: Obj): Boolean
|
||||
|
||||
external fun getNewArray(): Obj
|
||||
external fun testArray(obj: Obj): Boolean
|
||||
|
||||
external fun roundTrip(x: Obj): Obj
|
||||
|
||||
fun assertTrue(x: Boolean) {
|
||||
if (!x) error("assertTrue fail")
|
||||
}
|
||||
|
||||
fun assertFalse(x: Boolean) {
|
||||
if (x) error("assertFalse fail")
|
||||
}
|
||||
|
||||
fun multiCast(obj: Obj): Obj =
|
||||
obj
|
||||
as Any as Any
|
||||
as Any as? Any
|
||||
as Any as Any?
|
||||
as Any as? Any?
|
||||
as Any as Obj
|
||||
as Any as? Obj
|
||||
as Any as Obj?
|
||||
as Any as? Obj?
|
||||
|
||||
as Any? as Any
|
||||
as Any? as? Any
|
||||
as Any? as Any?
|
||||
as Any? as? Any?
|
||||
as Any? as Obj
|
||||
as Any? as? Obj
|
||||
as Any? as Obj?
|
||||
as Any? as? Obj?
|
||||
|
||||
as Obj as Any
|
||||
as Obj as? Any
|
||||
as Obj as Any?
|
||||
as Obj as? Any?
|
||||
as Obj as Obj
|
||||
as Obj as? Obj
|
||||
as Obj as Obj?
|
||||
as Obj as? Obj?
|
||||
|
||||
as Obj? as Any
|
||||
as Obj? as? Any
|
||||
as Obj? as Any?
|
||||
as Obj? as? Any?
|
||||
as Obj? as Obj
|
||||
as Obj? as? Obj
|
||||
as Obj? as Obj?
|
||||
as Obj? as? Obj?
|
||||
as Obj
|
||||
|
||||
class CustomClass
|
||||
interface CustomInterface
|
||||
|
||||
fun <T1 : Any, T2 : Any?, T3 : Obj> testWithTypeParameters(obj: Obj, checkObj: (Any?) -> Unit) {
|
||||
checkObj(obj as T1)
|
||||
checkObj(obj as T2)
|
||||
checkObj(obj as T3)
|
||||
checkObj(obj as? T1)
|
||||
checkObj(obj as? T2)
|
||||
checkObj(obj as? T3)
|
||||
checkObj(obj as T1?)
|
||||
checkObj(obj as T2?)
|
||||
checkObj(obj as T3?)
|
||||
checkObj(obj as? T1?)
|
||||
checkObj(obj as? T2?)
|
||||
checkObj(obj as? T3?)
|
||||
}
|
||||
|
||||
fun test(
|
||||
obj: Obj,
|
||||
anotherObj: Obj,
|
||||
checkObj: (Any?) -> Unit,
|
||||
stableIdentity: Boolean
|
||||
) {
|
||||
checkObj(obj)
|
||||
checkObj(obj as Any)
|
||||
checkObj(obj as? Any)
|
||||
checkObj(obj as Any?)
|
||||
checkObj(obj as? Any?)
|
||||
testWithTypeParameters<Any, Any?, Obj>(obj, checkObj)
|
||||
val subObj: SubObj = obj as SubObj
|
||||
checkObj(subObj)
|
||||
checkObj(subObj as Obj)
|
||||
val roundTrippedObj = roundTrip(obj)
|
||||
checkObj(roundTrippedObj)
|
||||
assertTrue(obj == roundTrippedObj)
|
||||
if (stableIdentity)
|
||||
assertTrue(obj === roundTrippedObj)
|
||||
|
||||
val castedObj = multiCast(roundTrippedObj)
|
||||
checkObj(castedObj)
|
||||
|
||||
assertTrue(obj is Any)
|
||||
assertTrue(obj is Any?)
|
||||
assertFalse(obj !is Any)
|
||||
assertFalse(obj !is Any?)
|
||||
|
||||
assertFalse(obj as Any is CustomClass)
|
||||
assertFalse(obj as Any is CustomInterface)
|
||||
|
||||
val objects = listOf<Obj>(obj, roundTrippedObj, castedObj)
|
||||
for (obj1 in objects) {
|
||||
checkObj(obj1)
|
||||
assertTrue(obj1.hashCode() == obj.hashCode())
|
||||
assertTrue(obj1.toString() == obj.toString())
|
||||
assertTrue(obj != anotherObj)
|
||||
assertTrue(obj !== anotherObj)
|
||||
for (obj2 in objects) {
|
||||
assertTrue(obj1 == obj2)
|
||||
if (stableIdentity)
|
||||
assertTrue(obj1 === obj2)
|
||||
assertTrue(obj1.hashCode() == obj2.hashCode())
|
||||
assertTrue(obj1.toString() == obj2.toString())
|
||||
}
|
||||
}
|
||||
assertTrue(objects.size == 3)
|
||||
assertTrue(objects.toSet().size == 1)
|
||||
assertTrue((objects + anotherObj).toSet().size == 2)
|
||||
}
|
||||
|
||||
fun testPrimitive(n: Int) {
|
||||
test(
|
||||
getPrimitive1(n),
|
||||
getPrimitive2(n),
|
||||
{ obj ->
|
||||
if (!testPrimitive(obj as Obj, n)) {
|
||||
error("Fail $n")
|
||||
}
|
||||
},
|
||||
stableIdentity = false,
|
||||
)
|
||||
}
|
||||
|
||||
class C(val x: Int)
|
||||
value class IC(val x: Int)
|
||||
|
||||
fun box(): String {
|
||||
for (i in 0..2) { // TODO: Symbols and BigInts are not supprted in Wasm
|
||||
testPrimitive(i)
|
||||
}
|
||||
|
||||
test(
|
||||
getNewObject(),
|
||||
getNewObject(),
|
||||
{ obj ->
|
||||
if (!testObject(obj as Obj)) {
|
||||
error("Fail object")
|
||||
}
|
||||
},
|
||||
stableIdentity = true,
|
||||
)
|
||||
|
||||
test(
|
||||
getNewArray(),
|
||||
getNewArray(),
|
||||
{ obj ->
|
||||
if (!testArray(obj as Obj)) {
|
||||
error("Fail object")
|
||||
}
|
||||
},
|
||||
stableIdentity = true,
|
||||
)
|
||||
|
||||
|
||||
val kotlinValues = listOf(10, "10", true, arrayOf(10), intArrayOf(20), C(10), IC(10))
|
||||
val otherValues = listOf(11, "11", false, arrayOf(12), intArrayOf(22), C(11), IC(11))
|
||||
|
||||
for ((value, otherValue) in kotlinValues.zip(otherValues)) {
|
||||
test(
|
||||
value as Obj,
|
||||
otherValues as Obj,
|
||||
{ obj ->
|
||||
if (obj !== value) {
|
||||
error("Fail custom class")
|
||||
}
|
||||
},
|
||||
stableIdentity = true,
|
||||
)
|
||||
}
|
||||
|
||||
return "OK"
|
||||
}
|
||||
@@ -9,6 +9,20 @@ fun makeC(x: Int): C = C(x)
|
||||
@JsExport
|
||||
fun getX(c: C): Int = c.x
|
||||
|
||||
@JsExport
|
||||
fun getString(s: String): String = "Test string $s";
|
||||
|
||||
@JsExport
|
||||
fun isEven(x: Int): Boolean = x % 2 == 0
|
||||
|
||||
external interface EI
|
||||
|
||||
@JsExport
|
||||
fun eiAsAny(ei: EI): Any = ei
|
||||
|
||||
@JsExport
|
||||
fun anyAsEI(any: Any): EI = any as EI
|
||||
|
||||
fun box(): String = "OK"
|
||||
|
||||
// FILE: jsExport__after.js
|
||||
@@ -16,4 +30,16 @@ fun box(): String = "OK"
|
||||
const c = main.makeC(300);
|
||||
if (main.getX(c) !== 300) {
|
||||
throw "Fail 1";
|
||||
}
|
||||
|
||||
if (main.getString("2") !== "Test string 2") {
|
||||
throw "Fail 2";
|
||||
}
|
||||
|
||||
if (main.isEven(31) !== false || main.isEven(10) !== true) {
|
||||
throw "Fail 3";
|
||||
}
|
||||
|
||||
if (main.anyAsEI(main.eiAsAny({x:10})).x !== 10) {
|
||||
throw "Fail 4";
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
// TARGET_BACKEND: WASM
|
||||
// WITH_RUNTIME
|
||||
|
||||
// FILE: externals.js
|
||||
|
||||
function roundTrip(x) { return x; }
|
||||
|
||||
// FILE: externals.kt
|
||||
|
||||
external fun roundTrip(x: EI?): EI?
|
||||
|
||||
fun assertTrue(x: Boolean) {
|
||||
if (!x) error("assertTrue fail")
|
||||
}
|
||||
|
||||
fun assertFalse(x: Boolean) {
|
||||
if (x) error("assertFalse fail")
|
||||
}
|
||||
|
||||
external interface EI
|
||||
|
||||
@JsFun("() => null")
|
||||
external fun getNull(): EI?
|
||||
|
||||
@JsFun("() => undefined")
|
||||
external fun getUndefined(): EI?
|
||||
|
||||
@JsFun("(ref) => ref === null")
|
||||
external fun isJsNull(ref: EI?): Boolean
|
||||
|
||||
@JsFun("(ref) => ref === undefined")
|
||||
external fun isJsUndefined(ref: EI?): Boolean
|
||||
|
||||
@JsFun("() => null")
|
||||
external fun getJsNullAsNonNullable(): EI
|
||||
|
||||
@JsFun("() => undefined")
|
||||
external fun getJsUndefinedAsNonNullable(): EI
|
||||
|
||||
fun box(): String {
|
||||
val jsNull = getNull()
|
||||
val jsUndefined = getUndefined()
|
||||
|
||||
assertTrue(jsNull == null)
|
||||
assertTrue((jsNull as Any?) == null)
|
||||
assertTrue((jsNull as Any?) === null)
|
||||
assertTrue(jsUndefined == null)
|
||||
|
||||
assertTrue(isJsNull(null))
|
||||
assertTrue(isJsNull(null as EI?))
|
||||
assertTrue(isJsNull(null as? EI?))
|
||||
assertTrue(isJsNull(roundTrip(null)))
|
||||
assertTrue(isJsNull(jsNull))
|
||||
|
||||
assertFalse(isJsUndefined(null))
|
||||
assertTrue(isJsUndefined(jsUndefined))
|
||||
|
||||
// TODO: Should these fail?
|
||||
val n2 = getJsNullAsNonNullable()
|
||||
val ud2 = getJsUndefinedAsNonNullable()
|
||||
assertTrue(isJsNull(n2))
|
||||
assertTrue(isJsUndefined(ud2))
|
||||
|
||||
return "OK"
|
||||
}
|
||||
+112
@@ -0,0 +1,112 @@
|
||||
// FILE: externals.js
|
||||
|
||||
// -- Strings --
|
||||
|
||||
function isTestString(x) {
|
||||
return x === "Test string";
|
||||
}
|
||||
|
||||
function getTestString() {
|
||||
return "Test string";
|
||||
}
|
||||
|
||||
function concatStrings(x, y) {
|
||||
if (typeof x !== "string") return "Fail 1";
|
||||
if (typeof y !== "string") return "Fail 2";
|
||||
return x + y;
|
||||
}
|
||||
|
||||
function concatStringsNullable(x, y) {
|
||||
return concatStrings(x ?? "<null>", y ?? "<null>")
|
||||
}
|
||||
|
||||
// -- Booleans --
|
||||
|
||||
function isTrueBoolean(x) {
|
||||
if (typeof x !== "boolean") return "Fail";
|
||||
return x === true;
|
||||
}
|
||||
|
||||
function isFalseBoolean(x) {
|
||||
if (typeof x !== "boolean") return "Fail";
|
||||
return x === false;
|
||||
}
|
||||
|
||||
function getTrueBoolean(x) {
|
||||
return true;
|
||||
}
|
||||
|
||||
function getFalseBoolean(x) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// -- Any --
|
||||
|
||||
function createJsObjectAsAny() {
|
||||
return { value: "object created by createJsObjectAsAny" };
|
||||
}
|
||||
|
||||
function createJsObjectAsExternalInterface() {
|
||||
return { value: "object created by createJsObjectAsExternalInterface" };
|
||||
}
|
||||
|
||||
function getObjectValueEI(x) {
|
||||
return x.value;
|
||||
}
|
||||
|
||||
function getObjectValueAny(x) {
|
||||
return x.value;
|
||||
}
|
||||
|
||||
// FILE: externals.kt
|
||||
|
||||
external fun isTestString(x: String): Boolean
|
||||
external fun getTestString(): String
|
||||
external fun concatStrings(x: String, y: String): String
|
||||
//external fun concatStringsNullable(x: String?, y: String?): String?
|
||||
|
||||
external fun isTrueBoolean(x: Boolean): Boolean
|
||||
external fun isFalseBoolean(x: Boolean): Boolean
|
||||
external fun getTrueBoolean(): Boolean
|
||||
external fun getFalseBoolean(): Boolean
|
||||
|
||||
external interface EI
|
||||
|
||||
external fun createJsObjectAsAny(): Any
|
||||
external fun createJsObjectAsExternalInterface(): EI
|
||||
external fun getObjectValueEI(x: EI): String
|
||||
external fun getObjectValueAny(x: Any): String
|
||||
|
||||
fun box(): String {
|
||||
// Strings
|
||||
if (!isTestString("Test string")) return "Fail !isTestString"
|
||||
if (isTestString("Test string 2")) return "Fail isTestString"
|
||||
if (getTestString() != "Test string") return "Fail getTestString"
|
||||
if (concatStrings("A", "B") != "AB") return "Fail concatStrings 1"
|
||||
if (concatStrings("Привет ", "😀\uD83D") != "Привет 😀\uD83D") return "Fail concatStrings 2"
|
||||
// if (concatStringsNullable("A", "B") != "AB") return "Fail concatStringsNullable 1"
|
||||
|
||||
// Boolean
|
||||
if (!isTrueBoolean(true)) return "Fail !isTrueBoolean"
|
||||
if (isTrueBoolean(false)) return "Fail isTrueBoolean"
|
||||
if (!isFalseBoolean(false)) return "Fail !isFalseBoolean"
|
||||
if (isFalseBoolean(true)) return "Fail isFalseBoolean"
|
||||
if (getTrueBoolean() != true) return "Fail getTrueBoolean"
|
||||
if (getFalseBoolean() != false) return "Fail getFalseBoolean"
|
||||
|
||||
// External interface
|
||||
val objAsEI: EI = createJsObjectAsExternalInterface()
|
||||
if (getObjectValueEI(objAsEI) != "object created by createJsObjectAsExternalInterface")
|
||||
return "Fail createJsObjectAsExternalInterface + getObjectValueEI"
|
||||
if (getObjectValueAny(objAsEI) != "object created by createJsObjectAsExternalInterface")
|
||||
return "Fail createJsObjectAsExternalInterface + getObjectValueAny"
|
||||
|
||||
// Any
|
||||
val objAsAny: Any = createJsObjectAsAny()
|
||||
if (getObjectValueAny(objAsAny) != "object created by createJsObjectAsAny")
|
||||
return "Fail createJsObjectAsAny + getObjectValueAny"
|
||||
if (getObjectValueEI(objAsAny as EI) != "object created by createJsObjectAsAny")
|
||||
return "Fail createJsObjectAsAny + getObjectValueEI"
|
||||
|
||||
return "OK"
|
||||
}
|
||||
Reference in New Issue
Block a user