[JS IR] Support nativeGetter, nativeSetter and nativeInvoke

#KT-41081 fixed
This commit is contained in:
Zalim Bashorov
2020-08-26 22:01:04 +03:00
parent c804319e65
commit ba846830c9
7 changed files with 97 additions and 57 deletions
@@ -1,5 +1,5 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
@@ -28,7 +28,8 @@ class CallsLowering(val context: JsIrBackendContext) : BodyLoweringPass {
EnumIntrinsicsTransformer(context),
ExceptionHelperCallsTransformer(context),
BuiltInConstructorCalls(context),
JsonIntrinsics(context)
JsonIntrinsics(context),
NativeGetterSetterTransformer(context),
)
override fun lower(irBody: IrBody, container: IrDeclaration) {
@@ -1,5 +1,5 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
@@ -14,38 +14,14 @@ import org.jetbrains.kotlin.ir.util.fqNameWhenAvailable
import org.jetbrains.kotlin.name.FqName
class JsonIntrinsics(val context: JsIrBackendContext) : CallsTransformer {
// TODO use nativeX annotations on kotlin.js.Json instead
class JsonIntrinsics(context: JsIrBackendContext) : NativeGetterSetterTransformer(context) {
override fun transformFunctionAccess(call: IrFunctionAccessExpression): IrExpression {
fun generateMemberAccess(): IrExpression {
val obj = call.dispatchReceiver!!
val propertyName = call.getValueArgument(0)!!
return IrDynamicOperatorExpressionImpl(
call.startOffset,
call.endOffset,
context.irBuiltIns.anyNType,
operator = IrDynamicOperator.ARRAY_ACCESS
).also {
it.receiver = obj
it.arguments.add(propertyName)
}
return when (call.symbol.owner.fqNameWhenAvailable) {
FqName("kotlin.js.Json.get") -> call.transformToIndexedRead()
FqName("kotlin.js.Json.set") -> call.transformToIndexedWrite()
else -> call
}
when (call.symbol.owner.fqNameWhenAvailable) {
FqName("kotlin.js.Json.get") ->
return generateMemberAccess()
FqName("kotlin.js.Json.set") -> {
val value = call.getValueArgument(1)!!
return IrDynamicOperatorExpressionImpl(call.startOffset, call.endOffset, call.type, IrDynamicOperator.EQ).also {
it.receiver = generateMemberAccess()
it.arguments.add(value)
}
}
}
return call
}
}
@@ -0,0 +1,54 @@
/*
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.ir.backend.js.lower.calls
import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext
import org.jetbrains.kotlin.ir.backend.js.utils.isJsNativeGetter
import org.jetbrains.kotlin.ir.backend.js.utils.isJsNativeSetter
import org.jetbrains.kotlin.ir.expressions.IrDynamicOperator
import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.expressions.IrFunctionAccessExpression
import org.jetbrains.kotlin.ir.expressions.impl.IrDynamicOperatorExpressionImpl
open class NativeGetterSetterTransformer(val context: JsIrBackendContext) : CallsTransformer {
override fun transformFunctionAccess(call: IrFunctionAccessExpression): IrExpression {
val callee = call.symbol.owner
return when {
callee.isJsNativeGetter() -> call.transformToIndexedRead()
callee.isJsNativeSetter() -> call.transformToIndexedWrite()
// @nativeInvoke is supported separately to simplify processing default arguments,
// it's harder to support using dynamic operator since the last one doesn't allow arguments with holes.
// The feature is implemented in `translateCall` in ir/backend/js/transformers/irToJs/jsAstUtils.kt
// callee.isJsNativeInvoke() -> {}
else -> call
}
}
protected fun IrFunctionAccessExpression.transformToIndexedRead(): IrExpression {
val obj = dispatchReceiver ?: extensionReceiver!!
val propertyName = getValueArgument(0)!!
return IrDynamicOperatorExpressionImpl(
startOffset,
endOffset,
context.irBuiltIns.anyNType,
operator = IrDynamicOperator.ARRAY_ACCESS
).also {
it.receiver = obj
it.arguments.add(propertyName)
}
}
protected fun IrFunctionAccessExpression.transformToIndexedWrite(): IrExpression {
val value = getValueArgument(1)!!
return IrDynamicOperatorExpressionImpl(startOffset, endOffset, type, IrDynamicOperator.EQ).also {
it.receiver = transformToIndexedRead()
it.arguments.add(value)
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
@@ -62,7 +62,7 @@ fun translateFunction(declaration: IrFunction, name: JsName?, context: JsGenerat
return function
}
private fun isNativeInvoke(receiver: JsExpression?, call: IrCall): Boolean {
private fun isFunctionTypeInvoke(receiver: JsExpression?, call: IrCall): Boolean {
if (receiver == null || receiver is JsThisRef) return false
val simpleFunction = call.symbol.owner as? IrSimpleFunction ?: return false
val receiverType = simpleFunction.dispatchReceiverParameter?.type ?: return false
@@ -101,8 +101,8 @@ fun translateCall(
}
}
if (isNativeInvoke(jsDispatchReceiver, expression)) {
return JsInvocation(jsDispatchReceiver!!, arguments)
if (isFunctionTypeInvoke(jsDispatchReceiver, expression) || expression.symbol.owner.isJsNativeInvoke()) {
return JsInvocation(jsDispatchReceiver ?: jsExtensionReceiver!!, arguments)
}
expression.superQualifierSymbol?.let { superQualifier ->
@@ -1,5 +1,5 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
@@ -21,6 +21,9 @@ object JsAnnotations {
val jsNameFqn = FqName("kotlin.js.JsName")
val jsQualifierFqn = FqName("kotlin.js.JsQualifier")
val jsExportFqn = FqName("kotlin.js.JsExport")
val jsNativeGetter = FqName("kotlin.js.nativeGetter")
val jsNativeSetter = FqName("kotlin.js.nativeSetter")
val jsNativeInvoke = FqName("kotlin.js.nativeInvoke")
}
@Suppress("UNCHECKED_CAST")
@@ -42,6 +45,12 @@ fun IrAnnotationContainer.getJsName(): String? =
fun IrAnnotationContainer.isJsExport(): Boolean =
hasAnnotation(JsAnnotations.jsExportFqn)
fun IrAnnotationContainer.isJsNativeGetter(): Boolean = hasAnnotation(JsAnnotations.jsNativeGetter)
fun IrAnnotationContainer.isJsNativeSetter(): Boolean = hasAnnotation(JsAnnotations.jsNativeSetter)
fun IrAnnotationContainer.isJsNativeInvoke(): Boolean = hasAnnotation(JsAnnotations.jsNativeInvoke)
fun IrDeclarationWithName.getJsNameOrKotlinName(): Name =
when (val jsName = getJsName()) {
null -> name
@@ -57,4 +66,4 @@ fun IrConstructorCall.associatedObject(): IrClass? {
if (!symbol.owner.constructedClass.isAssociatedObjectAnnotatedAnnotation) return null
val klass = ((getValueArgument(0) as? IrClassReference)?.symbol as? IrClassSymbol)?.owner ?: return null
return if (klass.isObject) klass else null
}
}
@@ -1,5 +1,3 @@
// IGNORE_BACKEND: JS_IR
// IGNORE_BACKEND: JS_IR_ES6
// EXPECTED_REACHABLE_NODES: 1291
package foo
@@ -91,4 +89,4 @@ fun box(): String {
testExtensions(b, 5, undefined, t)
return "OK"
}
}
+17 -15
View File
@@ -1,40 +1,42 @@
// IGNORE_BACKEND: JS_IR
// IGNORE_BACKEND: JS_IR_ES6
// EXPECTED_REACHABLE_NODES: 1286
// EXPECTED_REACHABLE_NODES: 1244
package foo
external class Function(vararg argsAndCode: String) {
external fun Function(vararg argsAndCode: String): Function
external interface Function {
@nativeInvoke
operator fun invoke(a: Any?): Any? = definedExternally
operator fun invoke(a: Any?): Any?
@nativeInvoke
fun baz(a: Any?, b: Any?): Any? = definedExternally
fun baz(a: Any?, b: Any? = definedExternally, c: Any? = definedExternally): Any?
}
@nativeInvoke
operator fun Function.invoke(a: Any?, b: Any?): Any? = definedExternally
@nativeInvoke
fun Function.bar(a: Any?, b: Any?): Any? = definedExternally
fun Function.bar(a: Any?, b: Any? = definedExternally, c: Any? = definedExternally): Any? = definedExternally
object t{}
fun box(): String {
val f = Function("a", "return a")
val g = Function("a", "b", "return a + b")
val g = Function("a", "b", "c", "return a + (b || 10) + (c || 100)")
assertEquals(1, f(1))
assertEquals("ok", f("ok"))
assertEquals(t, f(t))
assertEquals(5, g(1, 4))
assertEquals("ok34", g("ok", 34))
assertEquals(105, g(1, 4))
assertEquals("ok34100", g("ok", 34))
assertEquals(5, g.baz(1, 4))
assertEquals("ok34", g.baz("ok", 34))
assertEquals(105, g.baz(1, 4))
assertEquals("ok34100", g.baz("ok", 34))
assertEquals("ok1034", g.baz("ok", c = 34))
assertEquals(5, g.bar(1, 4))
assertEquals("ok34", g.bar("ok", 34))
assertEquals(105, g.bar(1, 4))
assertEquals("ok34100", g.bar("ok", 34))
assertEquals("ok1034", g.bar("ok", c = 34))
return "OK"
}
}