JS: fix issues with js() usage in inline functions

This commit is contained in:
Alexey Tsvetkov
2015-03-13 14:51:38 +03:00
parent 5ff5cea17a
commit 1557111277
11 changed files with 151 additions and 21 deletions
@@ -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<LabelScope>()
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, "<delegating scope to delegatingScope>") {
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)
}
@@ -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("<js checker>")), "<js fun>")
val statements = parse(code, errorReporter, parserScope)
if (statements.size() == 0) {
context.trace.report(ErrorsJs.JSCODE_NO_JAVASCRIPT_PRODUCED.on(argument))
@@ -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("<inline>")))
val moduleName = getExternalModuleName(descriptor)!!
val moduleNameLiteral = context.program().getStringLiteral(moduleName)
val moduleReference = context.namer().getModuleReference(moduleNameLiteral)
@@ -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<JsScope>();
{
scopes.push(rootScope)
init {
scopes.push(scope)
}
fun enterFunction(): JsFunction {
@@ -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<JsStatement> =
parse(code, 0, reporter, insideFunction, Parser::parse).toJsAst(JsAstMapper::mapStatements)
public fun parse(code: String, reporter: ErrorReporter, scope: JsScope): List<JsStatement> {
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<T>(mapAction: JsAstMapper.(Node)->T): T =
JsAstMapper(RootScope()).mapAction(this)
private fun Node.toJsAst<T>(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("<parser>"))
}
@@ -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");
@@ -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");
@@ -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("<js code>"));
JsScope scope = new DelegatingJsFunctionScopeWithTemporaryParent((JsFunctionScope) currentScope, temporaryRootScope);
return ParserPackage.parse(jsCode, ThrowExceptionOnErrorReporter.INSTANCE$, scope);
}
}
@@ -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"
}
@@ -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"
}
@@ -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"
}