From ba846830c9ccbdeae0e5556255c7c6f623079575 Mon Sep 17 00:00:00 2001 From: Zalim Bashorov Date: Wed, 26 Aug 2020 22:01:04 +0300 Subject: [PATCH] [JS IR] Support nativeGetter, nativeSetter and nativeInvoke #KT-41081 fixed --- .../backend/js/lower/calls/CallsLowering.kt | 5 +- .../backend/js/lower/calls/JsonIntrinsics.kt | 38 +++---------- .../calls/NativeGetterSetterTransformer.kt | 54 +++++++++++++++++++ .../js/transformers/irToJs/jsAstUtils.kt | 8 +-- .../ir/backend/js/utils/AnnotationUtils.kt | 13 ++++- .../box/native/nativeGetterAndNativeSetter.kt | 4 +- .../testData/box/native/nativeInvoke.kt | 32 +++++------ 7 files changed, 97 insertions(+), 57 deletions(-) create mode 100644 compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/calls/NativeGetterSetterTransformer.kt diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/calls/CallsLowering.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/calls/CallsLowering.kt index 024699e5d32..5555f0c3aea 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/calls/CallsLowering.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/calls/CallsLowering.kt @@ -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) { diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/calls/JsonIntrinsics.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/calls/JsonIntrinsics.kt index 240a84badb8..053d587a7f9 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/calls/JsonIntrinsics.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/calls/JsonIntrinsics.kt @@ -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 } } diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/calls/NativeGetterSetterTransformer.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/calls/NativeGetterSetterTransformer.kt new file mode 100644 index 00000000000..89f3fe57eb6 --- /dev/null +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/calls/NativeGetterSetterTransformer.kt @@ -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) + } + } +} diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/jsAstUtils.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/jsAstUtils.kt index c444bd86ed9..9c483f48805 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/jsAstUtils.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/jsAstUtils.kt @@ -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 -> diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/AnnotationUtils.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/AnnotationUtils.kt index ec5cf148d0d..0f787ec4e40 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/AnnotationUtils.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/AnnotationUtils.kt @@ -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 -} \ No newline at end of file +} diff --git a/js/js.translator/testData/box/native/nativeGetterAndNativeSetter.kt b/js/js.translator/testData/box/native/nativeGetterAndNativeSetter.kt index 568eee15449..773d41bf129 100644 --- a/js/js.translator/testData/box/native/nativeGetterAndNativeSetter.kt +++ b/js/js.translator/testData/box/native/nativeGetterAndNativeSetter.kt @@ -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" -} \ No newline at end of file +} diff --git a/js/js.translator/testData/box/native/nativeInvoke.kt b/js/js.translator/testData/box/native/nativeInvoke.kt index dc74f072679..b9f086273b4 100644 --- a/js/js.translator/testData/box/native/nativeInvoke.kt +++ b/js/js.translator/testData/box/native/nativeInvoke.kt @@ -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" -} \ No newline at end of file +}