[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:
Svyatoslav Kuzmich
2021-10-15 23:04:22 +03:00
parent baa53b5cf3
commit 4fc461a2ff
30 changed files with 1240 additions and 81 deletions
@@ -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"
}
+26
View File
@@ -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
View File
@@ -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"
}