diff --git a/js/js.dart-ast/src/com/google/dart/compiler/backend/js/ast/jsScopes.kt b/js/js.dart-ast/src/com/google/dart/compiler/backend/js/ast/jsScopes.kt index 59cd3ccec16..27a87a9d9dc 100644 --- a/js/js.dart-ast/src/com/google/dart/compiler/backend/js/ast/jsScopes.kt +++ b/js/js.dart-ast/src/com/google/dart/compiler/backend/js/ast/jsScopes.kt @@ -26,7 +26,7 @@ public object JsDynamicScope : JsScope(null, "Scope for dynamic declarations", n override fun doCreateName(name: String) = JsName(this, name) } -public class JsFunctionScope(parent: JsScope, description: String) : JsScope(parent, description, null) { +public open class JsFunctionScope(parent: JsScope, description: String) : JsScope(parent, description, null) { private val labelScopes = Stack() private val topLabelScope: LabelScope? @@ -36,20 +36,20 @@ public class JsFunctionScope(parent: JsScope, description: String) : JsScope(par override fun hasOwnName(name: String): Boolean = RESERVED_WORDS.contains(name) || super.hasOwnName(name) - public fun declareNameUnsafe(identifier: String): JsName = super.declareName(identifier) + public open fun declareNameUnsafe(identifier: String): JsName = super.declareName(identifier) - public fun enterLabel(label: String): JsName { + public open fun enterLabel(label: String): JsName { val scope = LabelScope(topLabelScope, label) labelScopes.push(scope) return scope.labelName } - public fun exitLabel() { + public open fun exitLabel() { assert(labelScopes.isNotEmpty()) { "No scope to exit from" } labelScopes.pop() } - public fun findLabel(label: String): JsName? = + public open fun findLabel(label: String): JsName? = topLabelScope?.findName(label) private inner class LabelScope(parent: LabelScope?, val ident: String) : JsScope(parent, "Label scope for $ident", null) { @@ -106,3 +106,36 @@ public class JsFunctionScope(parent: JsScope, description: String) : JsScope(par ) } } + +public class DelegatingJsFunctionScopeWithTemporaryParent( + private val delegatingScope: JsFunctionScope, + parent: JsScope +) : JsFunctionScope(parent, "") { + + override fun hasOwnName(name: String): Boolean = + delegatingScope.hasOwnName(name) + + override fun findOwnName(ident: String): JsName? = + delegatingScope.findOwnName(ident) + + override fun declareNameUnsafe(identifier: String): JsName = + delegatingScope.declareNameUnsafe(identifier) + + override fun declareName(identifier: String): JsName = + delegatingScope.declareName(identifier) + + override fun declareFreshName(suggestedName: String): JsName = + delegatingScope.declareFreshName(suggestedName) + + override fun declareTemporary(): JsName = + delegatingScope.declareTemporary() + + override fun enterLabel(label: String): JsName = + delegatingScope.enterLabel(label) + + override fun exitLabel() = + delegatingScope.exitLabel() + + override fun findLabel(label: String): JsName? = + delegatingScope.findLabel(label) +} 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 1dde587e6a8..fac2a7c154b 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 @@ -16,6 +16,9 @@ package org.jetbrains.kotlin.js.resolve.diagnostics +import com.google.dart.compiler.backend.js.ast.JsFunctionScope +import com.google.dart.compiler.backend.js.ast.JsProgram +import com.google.dart.compiler.backend.js.ast.JsRootScope import com.google.gwt.dev.js.parserExceptions.AbortParsingException import com.google.gwt.dev.js.rhino.* import com.google.gwt.dev.js.rhino.Utils.* @@ -85,7 +88,8 @@ public class JsCallChecker : CallChecker { val errorReporter = JsCodeErrorReporter(argument, code, context.trace) try { - val statements = parse(code, errorReporter, insideFunction = true) + val parserScope = JsFunctionScope(JsRootScope(JsProgram("")), "") + val statements = parse(code, errorReporter, parserScope) if (statements.size() == 0) { context.trace.report(ErrorsJs.JSCODE_NO_JAVASCRIPT_PRODUCED.on(argument)) diff --git a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/FunctionReader.kt b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/FunctionReader.kt index de59f25b555..888eb327d97 100644 --- a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/FunctionReader.kt +++ b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/FunctionReader.kt @@ -126,7 +126,7 @@ public class FunctionReader(private val context: TranslationContext) { offset++ } - val function = parseFunction(source, offset, ThrowExceptionOnErrorReporter) + val function = parseFunction(source, offset, ThrowExceptionOnErrorReporter, JsRootScope(JsProgram(""))) val moduleName = getExternalModuleName(descriptor)!! val moduleNameLiteral = context.program().getStringLiteral(moduleName) val moduleReference = context.namer().getModuleReference(moduleNameLiteral) diff --git a/js/js.parser/src/com/google/gwt/dev/js/ScopeContext.kt b/js/js.parser/src/com/google/gwt/dev/js/ScopeContext.kt index 5c76ed00a81..df7bef09d33 100644 --- a/js/js.parser/src/com/google/gwt/dev/js/ScopeContext.kt +++ b/js/js.parser/src/com/google/gwt/dev/js/ScopeContext.kt @@ -20,11 +20,12 @@ import com.google.dart.compiler.backend.js.ast.* import java.util.Stack -class ScopeContext(private val rootScope: JsScope) { +class ScopeContext(scope: JsScope) { + private val rootScope = sequence(scope) { it.getParent() }.first { it is JsRootScope } private val scopes = Stack(); - { - scopes.push(rootScope) + init { + scopes.push(scope) } fun enterFunction(): JsFunction { diff --git a/js/js.parser/src/org/jetbrains/kotlin/js/parser/parserUtils.kt b/js/js.parser/src/org/jetbrains/kotlin/js/parser/parserUtils.kt index 349070b6a20..2e87039ee68 100644 --- a/js/js.parser/src/org/jetbrains/kotlin/js/parser/parserUtils.kt +++ b/js/js.parser/src/org/jetbrains/kotlin/js/parser/parserUtils.kt @@ -27,14 +27,17 @@ import java.util.* private val FAKE_SOURCE_INFO = SourceInfoImpl(null, 0, 0, 0, 0) -public fun parse(code: String, reporter: ErrorReporter, insideFunction: Boolean): List = - parse(code, 0, reporter, insideFunction, Parser::parse).toJsAst(JsAstMapper::mapStatements) +public fun parse(code: String, reporter: ErrorReporter, scope: JsScope): List { + val insideFunction = scope is JsFunctionScope + val node = parse(code, 0, reporter, insideFunction, Parser::parse) + return node.toJsAst(scope, JsAstMapper::mapStatements) +} -public fun parseFunction(code: String, offset: Int, reporter: ErrorReporter): JsFunction = +public fun parseFunction(code: String, offset: Int, reporter: ErrorReporter, scope: JsScope): JsFunction = parse(code, offset, reporter, insideFunction = false) { addObserver(FunctionParsingObserver()) primaryExpr(it) - }.toJsAst(JsAstMapper::mapFunction) + }.toJsAst(scope, JsAstMapper::mapFunction) private class FunctionParsingObserver : Observer { var functionsStarted = 0 @@ -74,15 +77,11 @@ private fun parse( } inline -private fun Node.toJsAst(mapAction: JsAstMapper.(Node)->T): T = - JsAstMapper(RootScope()).mapAction(this) +private fun Node.toJsAst(scope: JsScope, mapAction: JsAstMapper.(Node)->T): T = + JsAstMapper(scope).mapAction(this) private fun StringReader(string: String, offset: Int): Reader { val reader = StringReader(string) reader.skip(offset.toLong()) return reader -} - -private fun RootScope(): JsScope { - return JsRootScope(JsProgram("")) } \ No newline at end of file diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/InlineJsTestGenerated.java b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/InlineJsTestGenerated.java index 666ea4d8922..6ae9b9508a2 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/InlineJsTestGenerated.java +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/InlineJsTestGenerated.java @@ -198,6 +198,18 @@ public class InlineJsTestGenerated extends AbstractInlineJsTest { doTest(fileName); } + @TestMetadata("jsCode.kt") + public void testJsCode() throws Exception { + String fileName = JetTestUtils.navigationMetadata("js/js.translator/testData/inline/cases/jsCode.kt"); + doTest(fileName); + } + + @TestMetadata("jsCodeVarDeclared.kt") + public void testJsCodeVarDeclared() throws Exception { + String fileName = JetTestUtils.navigationMetadata("js/js.translator/testData/inline/cases/jsCodeVarDeclared.kt"); + doTest(fileName); + } + @TestMetadata("lambdaInLambda.kt") public void testLambdaInLambda() throws Exception { String fileName = JetTestUtils.navigationMetadata("js/js.translator/testData/inline/cases/lambdaInLambda.kt"); diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/JsCodeTestGenerated.java b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/JsCodeTestGenerated.java index 9fd40c69177..9c65e0f099a 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/JsCodeTestGenerated.java +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/JsCodeTestGenerated.java @@ -102,6 +102,12 @@ public class JsCodeTestGenerated extends AbstractJsCodeTest { doTest(fileName); } + @TestMetadata("labelNestedClashWithKotlin.kt") + public void testLabelNestedClashWithKotlin() throws Exception { + String fileName = JetTestUtils.navigationMetadata("js/js.translator/testData/jsCode/cases/labelNestedClashWithKotlin.kt"); + doTest(fileName); + } + @TestMetadata("labelSiblingClash.kt") public void testLabelSiblingClash() throws Exception { String fileName = JetTestUtils.navigationMetadata("js/js.translator/testData/jsCode/cases/labelSiblingClash.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 ef712be20ca..3d7fea7172d 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 @@ -135,6 +135,14 @@ public final class CallExpressionTranslator extends AbstractCallExpressionTransl assert constant != null: "jsCode must be compile time string " + jsCodeExpression; String jsCode = (String) constant.getValue(); assert jsCode != null: jsCodeExpression.toString(); - return ParserPackage.parse(jsCode, ThrowExceptionOnErrorReporter.INSTANCE$, /* insideFunction= */ true); + + // Parser can change local or global scope. + // In case of js we want to keep new local names, + // but no new global ones. + JsScope currentScope = context().scope(); + assert currentScope instanceof JsFunctionScope: "Usage of js outside of function is unexpected"; + JsScope temporaryRootScope = new JsRootScope(new JsProgram("")); + JsScope scope = new DelegatingJsFunctionScopeWithTemporaryParent((JsFunctionScope) currentScope, temporaryRootScope); + return ParserPackage.parse(jsCode, ThrowExceptionOnErrorReporter.INSTANCE$, scope); } } diff --git a/js/js.translator/testData/inline/cases/jsCode.kt b/js/js.translator/testData/inline/cases/jsCode.kt new file mode 100644 index 00000000000..8a93ce00af0 --- /dev/null +++ b/js/js.translator/testData/inline/cases/jsCode.kt @@ -0,0 +1,14 @@ +package foo + +// CHECK_CONTAINS_NO_CALLS: test + +inline fun sum(x: Int, y: Int): Int = js("x + y") + +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/inline/cases/jsCodeVarDeclared.kt b/js/js.translator/testData/inline/cases/jsCodeVarDeclared.kt new file mode 100644 index 00000000000..3f4eb384801 --- /dev/null +++ b/js/js.translator/testData/inline/cases/jsCodeVarDeclared.kt @@ -0,0 +1,20 @@ +package foo + +// CHECK_CONTAINS_NO_CALLS: test + +inline fun sum(x: Int, y: Int): Int = js("var a = x; a + y") + +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/jsCode/cases/labelNestedClashWithKotlin.kt b/js/js.translator/testData/jsCode/cases/labelNestedClashWithKotlin.kt new file mode 100644 index 00000000000..eeee2efdb35 --- /dev/null +++ b/js/js.translator/testData/jsCode/cases/labelNestedClashWithKotlin.kt @@ -0,0 +1,33 @@ +package foo + +fun box(): String { + var sum = 0 + var sumInner = 0 + var sumOuter = 0 + + val range = 0..10 + val skipInner = 5 + val skipOuter = 8 + + + @block for (i in range) { + sum += i + + if (i == skipOuter) break@block + + js(""" + block: { + if (i === skipInner) break block; + + sumInner += i + } + """) + + sumOuter += i + } + + assertEquals(sum - skipOuter, sumOuter, "sumOuter") + assertEquals(sum - skipOuter - skipInner, sumInner, "sumInner") + + return "OK" +} \ No newline at end of file