[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. * 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), EnumIntrinsicsTransformer(context),
ExceptionHelperCallsTransformer(context), ExceptionHelperCallsTransformer(context),
BuiltInConstructorCalls(context), BuiltInConstructorCalls(context),
JsonIntrinsics(context) JsonIntrinsics(context),
NativeGetterSetterTransformer(context),
) )
override fun lower(irBody: IrBody, container: IrDeclaration) { 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. * 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 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 { override fun transformFunctionAccess(call: IrFunctionAccessExpression): IrExpression {
return when (call.symbol.owner.fqNameWhenAvailable) {
fun generateMemberAccess(): IrExpression { FqName("kotlin.js.Json.get") -> call.transformToIndexedRead()
val obj = call.dispatchReceiver!! FqName("kotlin.js.Json.set") -> call.transformToIndexedWrite()
val propertyName = call.getValueArgument(0)!! else -> call
return IrDynamicOperatorExpressionImpl(
call.startOffset,
call.endOffset,
context.irBuiltIns.anyNType,
operator = IrDynamicOperator.ARRAY_ACCESS
).also {
it.receiver = obj
it.arguments.add(propertyName)
}
} }
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. * 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 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 if (receiver == null || receiver is JsThisRef) return false
val simpleFunction = call.symbol.owner as? IrSimpleFunction ?: return false val simpleFunction = call.symbol.owner as? IrSimpleFunction ?: return false
val receiverType = simpleFunction.dispatchReceiverParameter?.type ?: return false val receiverType = simpleFunction.dispatchReceiverParameter?.type ?: return false
@@ -101,8 +101,8 @@ fun translateCall(
} }
} }
if (isNativeInvoke(jsDispatchReceiver, expression)) { if (isFunctionTypeInvoke(jsDispatchReceiver, expression) || expression.symbol.owner.isJsNativeInvoke()) {
return JsInvocation(jsDispatchReceiver!!, arguments) return JsInvocation(jsDispatchReceiver ?: jsExtensionReceiver!!, arguments)
} }
expression.superQualifierSymbol?.let { superQualifier -> 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. * 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 jsNameFqn = FqName("kotlin.js.JsName")
val jsQualifierFqn = FqName("kotlin.js.JsQualifier") val jsQualifierFqn = FqName("kotlin.js.JsQualifier")
val jsExportFqn = FqName("kotlin.js.JsExport") 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") @Suppress("UNCHECKED_CAST")
@@ -42,6 +45,12 @@ fun IrAnnotationContainer.getJsName(): String? =
fun IrAnnotationContainer.isJsExport(): Boolean = fun IrAnnotationContainer.isJsExport(): Boolean =
hasAnnotation(JsAnnotations.jsExportFqn) 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 = fun IrDeclarationWithName.getJsNameOrKotlinName(): Name =
when (val jsName = getJsName()) { when (val jsName = getJsName()) {
null -> name null -> name
@@ -57,4 +66,4 @@ fun IrConstructorCall.associatedObject(): IrClass? {
if (!symbol.owner.constructedClass.isAssociatedObjectAnnotatedAnnotation) return null if (!symbol.owner.constructedClass.isAssociatedObjectAnnotatedAnnotation) return null
val klass = ((getValueArgument(0) as? IrClassReference)?.symbol as? IrClassSymbol)?.owner ?: return null val klass = ((getValueArgument(0) as? IrClassReference)?.symbol as? IrClassSymbol)?.owner ?: return null
return if (klass.isObject) klass else 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 // EXPECTED_REACHABLE_NODES: 1291
package foo package foo
@@ -91,4 +89,4 @@ fun box(): String {
testExtensions(b, 5, undefined, t) testExtensions(b, 5, undefined, t)
return "OK" return "OK"
} }
+17 -15
View File
@@ -1,40 +1,42 @@
// IGNORE_BACKEND: JS_IR // EXPECTED_REACHABLE_NODES: 1244
// IGNORE_BACKEND: JS_IR_ES6
// EXPECTED_REACHABLE_NODES: 1286
package foo package foo
external class Function(vararg argsAndCode: String) { external fun Function(vararg argsAndCode: String): Function
external interface Function {
@nativeInvoke @nativeInvoke
operator fun invoke(a: Any?): Any? = definedExternally operator fun invoke(a: Any?): Any?
@nativeInvoke @nativeInvoke
fun baz(a: Any?, b: Any?): Any? = definedExternally fun baz(a: Any?, b: Any? = definedExternally, c: Any? = definedExternally): Any?
} }
@nativeInvoke @nativeInvoke
operator fun Function.invoke(a: Any?, b: Any?): Any? = definedExternally operator fun Function.invoke(a: Any?, b: Any?): Any? = definedExternally
@nativeInvoke @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{} object t{}
fun box(): String { fun box(): String {
val f = Function("a", "return a") 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(1, f(1))
assertEquals("ok", f("ok")) assertEquals("ok", f("ok"))
assertEquals(t, f(t)) assertEquals(t, f(t))
assertEquals(5, g(1, 4)) assertEquals(105, g(1, 4))
assertEquals("ok34", g("ok", 34)) assertEquals("ok34100", g("ok", 34))
assertEquals(5, g.baz(1, 4)) assertEquals(105, g.baz(1, 4))
assertEquals("ok34", g.baz("ok", 34)) assertEquals("ok34100", g.baz("ok", 34))
assertEquals("ok1034", g.baz("ok", c = 34))
assertEquals(5, g.bar(1, 4)) assertEquals(105, g.bar(1, 4))
assertEquals("ok34", g.bar("ok", 34)) assertEquals("ok34100", g.bar("ok", 34))
assertEquals("ok1034", g.bar("ok", c = 34))
return "OK" return "OK"
} }