diff --git a/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/bindingContextSlicesJs.kt b/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/bindingContextSlicesJs.kt index 95eb8f52e1f..75394db4be2 100644 --- a/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/bindingContextSlicesJs.kt +++ b/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/bindingContextSlicesJs.kt @@ -16,9 +16,14 @@ package org.jetbrains.kotlin.js.resolve +import org.jetbrains.kotlin.descriptors.FunctionDescriptor import org.jetbrains.kotlin.descriptors.ModuleDescriptor +import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall +import org.jetbrains.kotlin.resolve.scopes.LexicalScope import org.jetbrains.kotlin.serialization.js.ModuleKind import org.jetbrains.kotlin.util.slicedMap.BasicWritableSlice import org.jetbrains.kotlin.util.slicedMap.RewritePolicy @JvmField val MODULE_KIND = BasicWritableSlice(RewritePolicy.DO_NOTHING) + +@JvmField val LEXICAL_SCOPE_FOR_JS = BasicWritableSlice, LexicalScope>(RewritePolicy.DO_NOTHING) diff --git a/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/JsCallChecker.kt b/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/JsCallChecker.kt index 3df8635ea1b..600e206b441 100644 --- a/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/JsCallChecker.kt +++ b/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/JsCallChecker.kt @@ -26,11 +26,13 @@ import com.google.gwt.dev.js.rhino.Utils.isEndOfLine import com.intellij.openapi.util.TextRange import com.intellij.psi.PsiElement import org.jetbrains.kotlin.descriptors.CallableDescriptor +import org.jetbrains.kotlin.descriptors.FunctionDescriptor import org.jetbrains.kotlin.descriptors.SimpleFunctionDescriptor import org.jetbrains.kotlin.diagnostics.DiagnosticFactory1 import org.jetbrains.kotlin.js.parser.parse import org.jetbrains.kotlin.js.patterns.DescriptorPredicate import org.jetbrains.kotlin.js.patterns.PatternBuilder +import org.jetbrains.kotlin.js.resolve.LEXICAL_SCOPE_FOR_JS import org.jetbrains.kotlin.psi.KtCallExpression import org.jetbrains.kotlin.psi.KtExpression import org.jetbrains.kotlin.psi.KtLiteralStringTemplateEntry @@ -96,6 +98,9 @@ class JsCallChecker( } catch (e: AbortParsingException) { // ignore } + + @Suppress("UNCHECKED_CAST") + context.trace.record(LEXICAL_SCOPE_FOR_JS, resolvedCall as ResolvedCall, context.scope) } } diff --git a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/util/rewriteUtils.kt b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/util/rewriteUtils.kt index 63f73c8c85e..18eda89faa6 100644 --- a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/util/rewriteUtils.kt +++ b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/util/rewriteUtils.kt @@ -18,16 +18,12 @@ package org.jetbrains.kotlin.js.inline.util import com.google.dart.compiler.backend.js.ast.JsExpression import com.google.dart.compiler.backend.js.ast.JsName -import com.google.dart.compiler.backend.js.ast.JsNameRef import com.google.dart.compiler.backend.js.ast.JsNode import org.jetbrains.kotlin.js.inline.util.rewriters.NameReplacingVisitor -import org.jetbrains.kotlin.js.inline.util.rewriters.ReturnReplacingVisitor import org.jetbrains.kotlin.js.inline.util.rewriters.ThisReplacingVisitor -import java.util.IdentityHashMap - -fun replaceNames(node: T, replaceMap: IdentityHashMap): T { - return NameReplacingVisitor(replaceMap).accept(node)!! +fun replaceNames(node: T, replaceMap: Map): T { + return if (replaceMap.isEmpty()) node else NameReplacingVisitor(replaceMap).accept(node)!! } fun replaceThisReference(node: T, replacement: JsExpression) { diff --git a/js/js.libraries/src/core/core.kt b/js/js.libraries/src/core/core.kt index c6558ee01a5..2a8a202349e 100644 --- a/js/js.libraries/src/core/core.kt +++ b/js/js.libraries/src/core/core.kt @@ -32,7 +32,7 @@ public external fun js(code: String): dynamic = noImpl /** * Function corresponding to JavaScript's `typeof` operator */ -public inline fun jsTypeOf(a: Any?): String = js("Kotlin").jsTypeOf(a) +public inline fun jsTypeOf(a: Any?): String = js("typeof a") @library internal fun deleteProperty(`object`: Any, property: Any): Unit = noImpl \ No newline at end of file diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/BoxJsTestGenerated.java b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/BoxJsTestGenerated.java index f0c07695d6b..b23e73b0b21 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/BoxJsTestGenerated.java +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/BoxJsTestGenerated.java @@ -3806,6 +3806,18 @@ public class BoxJsTestGenerated extends AbstractBoxJsTest { doTest(fileName); } + @TestMetadata("jsCode.kt") + public void testJsCode() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/box/inline/jsCode.kt"); + doTest(fileName); + } + + @TestMetadata("jsCodeVarDeclared.kt") + public void testJsCodeVarDeclared() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/box/inline/jsCodeVarDeclared.kt"); + doTest(fileName); + } + @TestMetadata("lambdaInLambda.kt") public void testLambdaInLambda() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/box/inline/lambdaInLambda.kt"); @@ -4955,6 +4967,12 @@ public class BoxJsTestGenerated extends AbstractBoxJsTest { doTest(fileName); } + @TestMetadata("referenceToKotlin.kt") + public void testReferenceToKotlin() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/box/jsCode/referenceToKotlin.kt"); + doTest(fileName); + } + @TestMetadata("switch.kt") public void testSwitch() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/box/jsCode/switch.kt"); @@ -5681,12 +5699,6 @@ public class BoxJsTestGenerated extends AbstractBoxJsTest { doTest(fileName); } - @TestMetadata("jsTypeOf.kt") - public void testJsTypeOf() throws Exception { - String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/box/native/jsTypeOf.kt"); - doTest(fileName); - } - @TestMetadata("kt2209.kt") public void testKt2209() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/box/native/kt2209.kt"); diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/reference/CallExpressionTranslator.java b/js/js.translator/src/org/jetbrains/kotlin/js/translate/reference/CallExpressionTranslator.java index 3ea8a664645..9639ec2a9e6 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/reference/CallExpressionTranslator.java +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/reference/CallExpressionTranslator.java @@ -22,18 +22,24 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.kotlin.config.CommonConfigurationKeys; import org.jetbrains.kotlin.descriptors.*; +import org.jetbrains.kotlin.incremental.components.NoLookupLocation; +import org.jetbrains.kotlin.js.inline.util.CollectUtilsKt; +import org.jetbrains.kotlin.js.inline.util.RewriteUtilsKt; import org.jetbrains.kotlin.js.parser.ParserUtilsKt; +import org.jetbrains.kotlin.js.resolve.BindingContextSlicesJsKt; import org.jetbrains.kotlin.js.resolve.diagnostics.JsCallChecker; import org.jetbrains.kotlin.js.translate.callTranslator.CallTranslator; import org.jetbrains.kotlin.js.translate.context.TranslationContext; +import org.jetbrains.kotlin.name.Name; import org.jetbrains.kotlin.psi.KtCallExpression; import org.jetbrains.kotlin.psi.KtExpression; import org.jetbrains.kotlin.psi.ValueArgument; import org.jetbrains.kotlin.resolve.calls.callUtil.CallUtilKt; import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall; import org.jetbrains.kotlin.resolve.inline.InlineUtil; +import org.jetbrains.kotlin.resolve.scopes.LexicalScope; -import java.util.List; +import java.util.*; import static org.jetbrains.kotlin.js.resolve.diagnostics.JsCallChecker.isJsCall; import static org.jetbrains.kotlin.resolve.constants.evaluate.ConstantExpressionEvaluator.getConstant; @@ -97,18 +103,58 @@ public final class CallExpressionTranslator extends AbstractCallExpressionTransl List statements = parseJsCode(argumentExpression); int size = statements.size(); + JsNode node; if (size == 0) { - return JsLiteral.NULL; - } else if (size > 1) { - return new JsBlock(statements); - } else { + node = JsLiteral.NULL; + } + else if (size > 1) { + node = new JsBlock(statements); + } + else { JsStatement resultStatement = statements.get(0); if (resultStatement instanceof JsExpressionStatement) { - return ((JsExpressionStatement) resultStatement).getExpression(); + node = ((JsExpressionStatement) resultStatement).getExpression(); + } + else { + node = resultStatement; + } + } + + LexicalScope lexicalScope = context().bindingContext().get(BindingContextSlicesJsKt.LEXICAL_SCOPE_FOR_JS, resolvedCall); + Map replacements = new HashMap(); + if (lexicalScope != null) { + Set references = CollectUtilsKt.collectUsedNames(node); + references.removeAll(CollectUtilsKt.collectDefinedNames(node)); + + for (JsName name : references) { + VariableDescriptor variable = getVariableByName(lexicalScope, Name.identifier(name.getIdent())); + if (variable != null) { + replacements.put(name, ReferenceTranslator.translateAsValueReference(variable, context())); + } } - return resultStatement; + if (!replacements.isEmpty()) { + node = RewriteUtilsKt.replaceNames(node, replacements); + } } + + return node; + } + + @Nullable + private static VariableDescriptor getVariableByName(@NotNull LexicalScope scope, @NotNull Name name) { + while (true) { + Collection variables = scope.getContributedVariables(name, NoLookupLocation.FROM_BACKEND); + if (!variables.isEmpty()) { + return variables.size() == 1 ? variables.iterator().next() : null; + } + + if (!(scope.getParent() instanceof LexicalScope)) break; + LexicalScope parentScope = (LexicalScope) scope.getParent(); + if (scope.getOwnerDescriptor() != parentScope.getOwnerDescriptor()) break; + scope = parentScope; + } + return null; } @NotNull diff --git a/js/js.translator/testData/box/inline/jsCode.kt b/js/js.translator/testData/box/inline/jsCode.kt new file mode 100644 index 00000000000..5c2013cee92 --- /dev/null +++ b/js/js.translator/testData/box/inline/jsCode.kt @@ -0,0 +1,14 @@ +package foo + +// CHECK_CONTAINS_NO_CALLS: test + +internal inline fun sum(x: Int, y: Int): Int = js("x + y") + +internal fun test(x: Int, y: Int): Int = sum(sum(x, x), sum(y, y)) + +fun box(): String { + assertEquals(4, test(1, 1)) + assertEquals(8, test(1, 3)) + + return "OK" +} \ No newline at end of file diff --git a/js/js.translator/testData/box/inline/jsCodeVarDeclared.kt b/js/js.translator/testData/box/inline/jsCodeVarDeclared.kt new file mode 100644 index 00000000000..474e0277071 --- /dev/null +++ b/js/js.translator/testData/box/inline/jsCodeVarDeclared.kt @@ -0,0 +1,20 @@ +package foo + +// CHECK_CONTAINS_NO_CALLS: test + +internal inline fun sum(x: Int, y: Int): Int = js("var a = x; a + y") + +internal fun test(x: Int, y: Int): Int { + val xx = sum(x, x) + js("var a = 0;") + val yy = sum(y, y) + + return sum(xx, yy) +} + +fun box(): String { + assertEquals(4, test(1, 1)) + assertEquals(8, test(1, 3)) + + return "OK" +} \ No newline at end of file diff --git a/js/js.translator/testData/box/jsCode/referenceToKotlin.js b/js/js.translator/testData/box/jsCode/referenceToKotlin.js new file mode 100644 index 00000000000..5e992c4dc11 --- /dev/null +++ b/js/js.translator/testData/box/jsCode/referenceToKotlin.js @@ -0,0 +1,5 @@ +function p(x) { + return x + ";"; +} + +var _ = ""; \ No newline at end of file diff --git a/js/js.translator/testData/box/jsCode/referenceToKotlin.kt b/js/js.translator/testData/box/jsCode/referenceToKotlin.kt new file mode 100644 index 00000000000..c24dbdd6b73 --- /dev/null +++ b/js/js.translator/testData/box/jsCode/referenceToKotlin.kt @@ -0,0 +1,120 @@ +external fun p(m: String): String + +var log = "" + +fun test1(p: String): String { + log += p("start1") + return js("return p;") +} + +fun test2(): String { + val p = "test2" + log += p("start2") + return js("p") +} + +fun test3(): String { + val p = "wrong3" + return js("var p = 'test3'; p") +} + +fun test4(): String { + return js("p('test4')") +} + +fun test5(): String { + val p = "wrong5" + fun f() = js("p('test5')") + return f() +} + +fun test6(): String { + log += p("start6") + val p = "test6" + return js(""" + var x = p; + var g = function(p) { + return x + p; + }; + g("-ok"); + """) +} + +fun test7(): String { + log += p("start7") + val p = "test7" + return js(""" + var g = function() { + return p; + }; + g(); + """) +} + +fun test8(): String { + val p = "wrong8" + val list = listOf("t", "e", "s", "t", "8") + var result = "" + for (p in list) { + result += js("p") + } + return result +} + +fun test9(): String { + val p = "wrong9" + val list = listOf("t" to "e", "s" to "t", "9" to "!") + var result = "" + for ((p, q) in list) { + result += js("p") + result += js("q") + } + return result +} + +fun test10(): String { + val list = listOf("O" to "K") + var result = "" + for ((p, _) in list) { + result += js("typeof p") + result += ";" + result += js("typeof _") + } + return result +} + +fun box(): String { + var result = test1("test1") + if (result != "test1") return "fail1: $result" + + result = test2() + if (result != "test2") return "fail2: $result" + + result = test3() + if (result != "test3") return "fail3: $result" + + result = test4() + if (result != "test4;") return "fail4: $result" + + result = test5() + if (result != "test5;") return "fail5: $result" + + result = test6() + if (result != "test6-ok") return "fail6: $result" + + result = test7() + if (result != "test7") return "fail7: $result" + + result = test8() + if (result != "test8") return "fail8: $result" + + result = test9() + if (result != "test9!") return "fail9: $result" + + result = test10() + if (result != "string;object") return "fail10: $result" + + if (log != "start1;start2;start6;start7;") return "fail log: $log" + + return "OK" +} \ No newline at end of file diff --git a/js/js.translator/testData/box/native/jsTypeOf.kt b/js/js.translator/testData/box/native/jsTypeOf.kt deleted file mode 100644 index c8b623e2f6d..00000000000 --- a/js/js.translator/testData/box/native/jsTypeOf.kt +++ /dev/null @@ -1,18 +0,0 @@ -fun box(): String { - var result = jsTypeOf(23) - if (result != "number") return "fail1: $result" - - result = jsTypeOf("23") - if (result != "string") return "fail2: $result" - - result = jsTypeOf({ x: Int -> x }) - if (result != "function") return "fail3: $result" - - result = jsTypeOf(object {}) - if (result != "object") return "fail4: $result" - - result = jsTypeOf(true) - if (result != "boolean") return "fail5: $result" - - return "OK" -} \ No newline at end of file diff --git a/js/js.translator/testData/kotlin_lib_ecma5.js b/js/js.translator/testData/kotlin_lib_ecma5.js index 933beda7bb2..004b6ecbb44 100644 --- a/js/js.translator/testData/kotlin_lib_ecma5.js +++ b/js/js.translator/testData/kotlin_lib_ecma5.js @@ -270,8 +270,6 @@ return cache.value; } - Kotlin.jsTypeOf = function(a) { return typeof a; }; - ////////////////////////////////// packages & modules ////////////////////////////// Kotlin.modules = {};