Interop with native JavaScript

Goal

Provide the ways to interact with native JavaScript.

Type-safe Declarations

Annotation native

TODO

Annotation nativeInvoke

Calls to functions annotated by nativeInvoke will be translated to calls of receiver with the arguments provided for original call.

Applicable to:

  • member functions of native declarations
  • non-member extension functions

Example:

native
class A {
    nativeInvoke
    fun invoke(): String = noImpl

    nativeInvoke
    fun foo(a: Int): Int = noImpl
}

fun A.bar(a: String): Int = noImpl


fun test(baz: A) {
	baz()
	baz.invoke()
	baz.foo(1)
	baz.bar("str")
}

Function test will be translated to:

...
test: function (baz) {
	foo()
	foo()
	foo(1)
	foo("str")
}
...

Annotation nativeGetter

Calls to functions annotated by nativeGetter will be translated to square/index operation on the receiver with the argument provided for original call.

Applicable to:

  • member functions of native declarations
  • non-member extension functions

Requirements:

  • must have exactly one argument
  • type of the argument must be String or subtype of Number
  • default values are prohibited
  • return type must be nullable

Example:

native
class A {
    nativeGetter
    fun get(a: String): String? = noImpl

    nativeGetter
    fun foo(a: Int): Int? = noImpl
}

class B

nativeGetter
fun B.get(a: String): Int? = noImpl

nativeGetter
fun B.bar(a: Int): Int? = noImpl

fun test(a: A, b: B) {
	a["foo"]
	a.get("bar")
	a.foo(1)
	b["foo"]
	b.get("bar")
	b.bar(1)
}

Function test will be translated to:

...
test: function (a, b) {
	a["foo"]
	a["bar"]
	a[1]
	b["foo"]
	b["bar"]
	b[1]
}
...

Annotation nativeSetter

Calls of functions annotated by nativeSetter will be translated to assignment of the second argument to the receiver indexed (with square/index operation) with the first argument.

Applicable to:

  • member functions of native declarations
  • non-member extension functions

Requirements:

  • must have exactly two arguments
  • type of the first argument must be String or subtype of Number
  • default values are prohibited
  • the return type is either Unit or a supertype of the second parameter's type

Example:

native
class A {
    nativeSetter
    fun set(a: String, v: Any) {}

    nativeSetter
    fun foo(a: Int, v: A) {}
}

class B

nativeSetter
fun B.set(a: String, v: B) {}

nativeSetter
fun B.bar(a: String, v: B?) {}

fun test(a: A, b: B) {
	a["foo"] = "text"
	a.set("bar", "value")
	a.foo(1, A())
	b["foo"] = B()
	b.set("bar", b)
	b.bar("a", null)
}

Function test will be translated to:

...
test: function (a, b) {
	a["foo"] = "text"
	a["bar"] = "value"
	a[1] = A()
	b["foo"] = B()
	b["bar"] = b
	b["a"] = null
}
...

Function js

Argument of js function is parsed as JavaScript code and injected directly into the JavaScript code generated by the compiler.

Requirements:

  • the argument should be a compile time constant of type String

Example:

fun test1() {
	js("console.log('Hello')")
}

fun test2(a: String) = js(""" 
	var r = foo(a);
	return r;
""")

is translated to:

function test1() {
	console.log('Hello')	
}

function test2(a) {
	var r = foo(a);
	return r;
}

Dynamic types

All dynamic calls with explicit names (regular and infix function calls, and property calls) are translated “as is”, without mangling. Additionally, many operations when applied to a receiver of type dynamic are translated “as is”, instead of by convention.

Operations translated “as is” to JavaScript:

  • binary: +, -, *, /, %, >, < >=, <=, ==, !=, ===, !==, &&, ||
  • unary
  • prefix: -, +, !
  • prefix and postfix: ++, --
  • assignments: +=, -=, *=, /=, %=
  • indexed access:
  • read: d[a], more than one argument is an error
  • write: d[a1] = a2, more than one argument in [] is an error
  • in and !in are forbidden, an error is reported

Note:

  • .. is translated to a call to rangeTo
  • ~, |, & and ^ are not supported (there's no Kotlin code that translates to these operations)