diff --git a/js/js.frontend/src/org/jetbrains/k2js/PredefinedAnnotation.kt b/js/js.frontend/src/org/jetbrains/k2js/PredefinedAnnotation.kt index 55072cfd0c2..d73a40484cd 100644 --- a/js/js.frontend/src/org/jetbrains/k2js/PredefinedAnnotation.kt +++ b/js/js.frontend/src/org/jetbrains/k2js/PredefinedAnnotation.kt @@ -16,7 +16,17 @@ package org.jetbrains.k2js +import kotlin.properties.Delegates + public enum class PredefinedAnnotation(public val fqName: String) { LIBRARY : PredefinedAnnotation("kotlin.js.library") NATIVE : PredefinedAnnotation("kotlin.js.native") + NATIVE_INVOKE : PredefinedAnnotation("kotlin.js.nativeInvoke") + NATIVE_GETTER : PredefinedAnnotation("kotlin.js.nativeGetter") + NATIVE_SETTER : PredefinedAnnotation("kotlin.js.nativeSetter") + + class object { + // TODO: replace with straight assignment when KT-5761 will be fixed + val WITH_CUSTOM_NAME by Delegates.lazy { setOf(LIBRARY, NATIVE) } + } } diff --git a/js/js.libraries/src/core/annotations.kt b/js/js.libraries/src/core/annotations.kt index eaaa2100351..f2d93397d0d 100644 --- a/js/js.libraries/src/core/annotations.kt +++ b/js/js.libraries/src/core/annotations.kt @@ -18,6 +18,13 @@ package kotlin.js native public annotation class native(public val name: String = "") +native +public annotation class nativeGetter +native +public annotation class nativeSetter +native +public annotation class nativeInvoke + native public annotation class library(public val name: String = "") native diff --git a/js/js.tests/test/org/jetbrains/k2js/test/semantics/NativeInteropTest.java b/js/js.tests/test/org/jetbrains/k2js/test/semantics/NativeInteropTest.java index b4392167aa9..e55faa142dd 100644 --- a/js/js.tests/test/org/jetbrains/k2js/test/semantics/NativeInteropTest.java +++ b/js/js.tests/test/org/jetbrains/k2js/test/semantics/NativeInteropTest.java @@ -134,4 +134,12 @@ public final class NativeInteropTest extends SingleFileTranslationTest { public void testPrint() throws Exception { checkFooBoxIsOk(); } + + public void testNativeInvoke() throws Exception { + checkFooBoxIsOk(); + } + + public void testNativeGetterAndNativeSetter() throws Exception { + checkFooBoxIsOk(); + } } diff --git a/js/js.translator/src/org/jetbrains/k2js/translate/callTranslator/FunctionCallCases.kt b/js/js.translator/src/org/jetbrains/k2js/translate/callTranslator/FunctionCallCases.kt index c0d29f55e33..9cffc92806f 100644 --- a/js/js.translator/src/org/jetbrains/k2js/translate/callTranslator/FunctionCallCases.kt +++ b/js/js.translator/src/org/jetbrains/k2js/translate/callTranslator/FunctionCallCases.kt @@ -19,24 +19,22 @@ package org.jetbrains.k2js.translate.callTranslator import com.google.dart.compiler.backend.js.ast.JsExpression import com.google.dart.compiler.backend.js.ast.JsNameRef import com.google.dart.compiler.backend.js.ast.JsInvocation -import java.util.Collections import java.util.ArrayList import org.jetbrains.k2js.translate.context.Namer import org.jetbrains.jet.lang.descriptors.CallableDescriptor import com.google.dart.compiler.backend.js.ast.JsNew import org.jetbrains.jet.lang.descriptors.ConstructorDescriptor import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns -import org.jetbrains.k2js.translate.general.Translation import com.google.dart.compiler.backend.js.ast.JsLiteral import com.google.dart.compiler.backend.js.ast.JsName import org.jetbrains.k2js.translate.context.TranslationContext import org.jetbrains.k2js.translate.reference.CallArgumentTranslator -import org.jetbrains.jet.lang.descriptors.CallableMemberDescriptor import org.jetbrains.jet.lang.descriptors.Visibilities -import org.jetbrains.jet.lang.psi.Call.CallType import com.intellij.util.SmartList -import org.jetbrains.k2js.translate.utils.JsDescriptorUtils -import org.jetbrains.jet.lang.resolve.DescriptorUtils +import com.google.dart.compiler.backend.js.ast.JsArrayAccess +import org.jetbrains.k2js.translate.utils.JsAstUtils +import org.jetbrains.k2js.translate.utils.AnnotationsUtils +import org.jetbrains.k2js.PredefinedAnnotation public fun addReceiverToArgs(receiver: JsExpression, arguments: List): List { if (arguments.isEmpty()) @@ -140,6 +138,32 @@ object DelegateFunctionIntrinsic : DelegateIntrinsic { } } +abstract class AnnotatedAsNativeXCallCase(val annotation: PredefinedAnnotation) : FunctionCallCase { + abstract fun translateCall(receiver: JsExpression, argumentsInfo: CallArgumentTranslator.ArgumentsInfo): JsExpression + + fun canApply(callInfo: FunctionCallInfo): Boolean = AnnotationsUtils.hasAnnotation(callInfo.callableDescriptor, annotation) + + final override fun FunctionCallInfo.dispatchReceiver() = translateCall(dispatchReceiver!!, argumentsInfo) + final override fun FunctionCallInfo.extensionReceiver() = translateCall(extensionReceiver!!, argumentsInfo) +} + +object NativeInvokeCallCase : AnnotatedAsNativeXCallCase(PredefinedAnnotation.NATIVE_INVOKE) { + override fun translateCall(receiver: JsExpression, argumentsInfo: CallArgumentTranslator.ArgumentsInfo) = + JsInvocation(receiver, argumentsInfo.getTranslateArguments()) +} + +object NativeGetterCallCase : AnnotatedAsNativeXCallCase(PredefinedAnnotation.NATIVE_GETTER) { + override fun translateCall(receiver: JsExpression, argumentsInfo: CallArgumentTranslator.ArgumentsInfo) = + JsArrayAccess(receiver, argumentsInfo.getTranslateArguments()[0]) +} + +object NativeSetterCallCase : AnnotatedAsNativeXCallCase(PredefinedAnnotation.NATIVE_SETTER) { + override fun translateCall(receiver: JsExpression, argumentsInfo: CallArgumentTranslator.ArgumentsInfo): JsExpression { + val args = argumentsInfo.getTranslateArguments() + return JsAstUtils.assignment(JsArrayAccess(receiver, args[0]), args[1]) + } +} + object InvokeIntrinsic : FunctionCallCase { fun canApply(callInfo: FunctionCallInfo): Boolean { if (!callInfo.callableDescriptor.getName().asString().equals("invoke")) @@ -212,6 +236,14 @@ fun FunctionCallInfo.translateFunctionCall(): JsExpression { return when { intrinsic != null -> intrinsic + + NativeInvokeCallCase.canApply(this) -> + NativeInvokeCallCase.translate(this) + NativeGetterCallCase.canApply(this) -> + NativeGetterCallCase.translate(this) + NativeSetterCallCase.canApply(this) -> + NativeSetterCallCase.translate(this) + InvokeIntrinsic.canApply(this) -> InvokeIntrinsic.translate(this) ConstructorCallCase.canApply(this) -> diff --git a/js/js.translator/src/org/jetbrains/k2js/translate/utils/AnnotationsUtils.java b/js/js.translator/src/org/jetbrains/k2js/translate/utils/AnnotationsUtils.java index d2e6cfadbbe..de5971b3160 100644 --- a/js/js.translator/src/org/jetbrains/k2js/translate/utils/AnnotationsUtils.java +++ b/js/js.translator/src/org/jetbrains/k2js/translate/utils/AnnotationsUtils.java @@ -40,8 +40,10 @@ public final class AnnotationsUtils { private AnnotationsUtils() { } - private static boolean hasAnnotation(@NotNull DeclarationDescriptor descriptor, - @NotNull PredefinedAnnotation annotation) { + public static boolean hasAnnotation( + @NotNull DeclarationDescriptor descriptor, + @NotNull PredefinedAnnotation annotation + ) { return getAnnotationByName(descriptor, annotation) != null; } @@ -95,7 +97,7 @@ public final class AnnotationsUtils { } for (DeclarationDescriptor descriptor : descriptors) { - for (PredefinedAnnotation annotation : PredefinedAnnotation.values()) { + for (PredefinedAnnotation annotation : PredefinedAnnotation.OBJECT$.getWITH_CUSTOM_NAME()) { if (!hasAnnotationOrInsideAnnotatedClass(descriptor, annotation)) { continue; } diff --git a/js/js.translator/testData/native/cases/nativeGetterAndNativeSetter.kt b/js/js.translator/testData/native/cases/nativeGetterAndNativeSetter.kt new file mode 100644 index 00000000000..cada6c3467f --- /dev/null +++ b/js/js.translator/testData/native/cases/nativeGetterAndNativeSetter.kt @@ -0,0 +1,95 @@ +package foo + +native("Object") +class JsObject { + nativeGetter + fun get(a: String): Any? = noImpl + + nativeSetter + fun set(a: String, v: Any?): Unit = noImpl + + nativeGetter + fun take(a: Int): Any? = noImpl + + nativeSetter + fun put(a: Int, v: Any?): Unit = noImpl +} + +nativeGetter +fun JsObject.get(a: Int): Any? = noImpl + +nativeSetter +fun JsObject.set(a: Int, v: Any?): Unit = noImpl + +nativeGetter +fun JsObject.take(a: String): Any? = noImpl + +nativeSetter +fun JsObject.put(a: String, v: Any?): Unit = noImpl + + +object t{} + +native +val undefined: Any = noImpl + +native +fun getTestObject(): JsObject = noImpl + +fun test(obj: JsObject, key: String, oldValue: Any, newValue: Any) { + assertEquals(oldValue, obj[key]) + obj[key] = newValue + assertEquals(newValue, obj[key]) + obj[key] = null + assertEquals(null, obj[key]) +} + +fun test(obj: JsObject, key: Int, oldValue: Any, newValue: Any) { + assertEquals(oldValue, obj.take(key)) + obj.put(key, newValue) + assertEquals(newValue, obj.take(key)) + obj.put(key, null) + assertEquals(null, obj.take(key)) +} + +fun testExtensions(obj: JsObject, key: Int, oldValue: Any, newValue: Any) { + assertEquals(oldValue, obj[key]) + obj[key] = newValue + assertEquals(newValue, obj[key]) + obj[key] = null + assertEquals(null, obj[key]) +} + +fun testExtensions(obj: JsObject, key: String, oldValue: Any, newValue: Any) { + assertEquals(oldValue, obj.take(key)) + obj.put(key, newValue) + assertEquals(newValue, obj.take(key)) + obj.put(key, null) + assertEquals(null, obj.take(key)) +} + +fun box(): String { + val a = getTestObject() + + test(a, "foo", "boo", "moo") + test(a, "bar", 35, 67) + test(a, "baz", undefined, 34) + test(a, "qoox", undefined, t) + test(a, 0, "ok", "OK!") + test(a, 1, 2, 3) + test(a, 2, undefined, "HI") + test(a, 5, undefined, t) + + val b = getTestObject() + + testExtensions(b, "foo", "boo", "moo") + testExtensions(b, "bar", 35, 67) + testExtensions(b, "baz", undefined, 34) + testExtensions(b, "qoox", undefined, t) + testExtensions(b, 0, "ok", "OK!") + testExtensions(b, 1, 2, 3) + testExtensions(b, 2, undefined, "HI") + testExtensions(b, 5, undefined, t) + + return "OK" +} \ No newline at end of file diff --git a/js/js.translator/testData/native/cases/nativeInvoke.kt b/js/js.translator/testData/native/cases/nativeInvoke.kt new file mode 100644 index 00000000000..57bbbc26a65 --- /dev/null +++ b/js/js.translator/testData/native/cases/nativeInvoke.kt @@ -0,0 +1,38 @@ +package foo + +native +class Function(vararg argsAndCode: String) { + nativeInvoke + fun invoke(a: Any?): Any? = noImpl + + nativeInvoke + fun baz(a: Any?, b: Any?): Any? = noImpl +} + +nativeInvoke +fun Function.invoke(a: Any?, b: Any?): Any? = noImpl + +nativeInvoke +fun Function.bar(a: Any?, b: Any?): Any? = noImpl + +object t{} + +fun box(): String { + val f = Function("a", "return a") + val g = Function("a", "b", "return a + b") + + assertEquals(1, f(1)) + assertEquals("ok", f("ok")) + assertEquals(t, f(t)) + + assertEquals(5, g(1, 4)) + assertEquals("ok34", g("ok", 34)) + + assertEquals(5, g.baz(1, 4)) + assertEquals("ok34", g.baz("ok", 34)) + + assertEquals(5, g.bar(1, 4)) + assertEquals("ok34", g.bar("ok", 34)) + + return "OK" +} \ No newline at end of file diff --git a/js/js.translator/testData/native/native/nativeGetterAndNativeSetter.js b/js/js.translator/testData/native/native/nativeGetterAndNativeSetter.js new file mode 100644 index 00000000000..5023531cba6 --- /dev/null +++ b/js/js.translator/testData/native/native/nativeGetterAndNativeSetter.js @@ -0,0 +1,8 @@ +function getTestObject() { + return { + "foo" : "boo", + "bar" : 35, + 0 : "ok", + 1 : 2 + }; +}; \ No newline at end of file