From 1f87bea78f94b53251b74a063d4ab1ec6680ba53 Mon Sep 17 00:00:00 2001 From: Alexey Tsvetkov Date: Sun, 19 Oct 2014 05:15:39 +0400 Subject: [PATCH] JS backend: added support for nested labels with the same name #KT-5891 fixed --- .../dart/compiler/backend/js/ast/JsScope.java | 3 +- .../dart/compiler/backend/js/ast/jsScopes.kt | 32 +++++++++++++ .../org/jetbrains/k2js/inline/JsInliner.java | 3 ++ .../inline/util/collectors/NameCollector.kt | 5 -- .../jetbrains/k2js/inline/util/namingUtils.kt | 13 ++++++ .../rewriters/LabelNameRefreshingVisitor.kt | 46 +++++++++++++++++++ .../expression/ExpressionVisitor.java | 19 +++++--- 7 files changed, 107 insertions(+), 14 deletions(-) create mode 100644 js/js.inliner/src/org/jetbrains/k2js/inline/util/rewriters/LabelNameRefreshingVisitor.kt diff --git a/js/js.dart-ast/src/com/google/dart/compiler/backend/js/ast/JsScope.java b/js/js.dart-ast/src/com/google/dart/compiler/backend/js/ast/JsScope.java index 1553153a0c0..1c57222aa4b 100644 --- a/js/js.dart-ast/src/com/google/dart/compiler/backend/js/ast/JsScope.java +++ b/js/js.dart-ast/src/com/google/dart/compiler/backend/js/ast/JsScope.java @@ -53,7 +53,6 @@ public abstract class JsScope { private static final Pattern FRESH_NAME_SUFFIX = Pattern.compile("[\\$_]\\d+$"); public JsScope(JsScope parent, @NotNull String description, @Nullable String scopeId) { - assert (parent != null); this.scopeId = scopeId; this.description = description; this.parent = parent; @@ -196,7 +195,7 @@ public abstract class JsScope { * Fresh name for "a0" should still be "a0_0". */ @NotNull - private String getFreshIdent(@NotNull String suggestedIdent) { + protected String getFreshIdent(@NotNull String suggestedIdent) { char sep = '_'; String baseName = suggestedIdent; int counter = 0; 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 121e7e29e5c..00b74a4a297 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 @@ -16,18 +16,50 @@ package com.google.dart.compiler.backend.js.ast +import java.util.Stack public fun JsObjectScope(parent: JsScope, description: String): JsObjectScope = JsObjectScope(parent, description, null) public class JsObjectScope(parent: JsScope, description: String, scopeId: String?) : JsScope(parent, description, scopeId) public class JsFunctionScope(parent: JsScope, description: String) : JsScope(parent, description, null) { + + private val labelScopes = Stack() + private val topLabelScope: LabelScope? + get() = if (labelScopes.notEmpty) labelScopes.peek() else null + override fun declareName(identifier: String): JsName = super.declareFreshName(identifier) override fun hasOwnName(name: String): Boolean = RESERVED_WORDS.contains(name) || super.hasOwnName(name) public fun declareNameUnsafe(identifier: String): JsName = super.declareName(identifier) + public fun enterLabel(label: String): JsName { + val scope = LabelScope(topLabelScope, label) + labelScopes.push(scope) + return scope.labelName + } + + public fun exitLabel() { + assert(labelScopes.notEmpty) { "No scope to exit from" } + labelScopes.pop() + } + + public fun findLabel(label: String): JsName? = + topLabelScope?.findName(label) + + private inner class LabelScope(parent: LabelScope?, val ident: String) : JsScope(parent, "Label scope for $ident", null) { + val labelName = JsName(this@JsFunctionScope, parent?.getFreshIdent(ident) ?: ident) + + override fun findOwnName(name: String): JsName? = + if (name == ident) labelName else null + + override fun hasOwnName(name: String): Boolean = + name == ident + || name == labelName.getIdent() + || getParent()?.hasOwnName(name) ?: false + } + class object { public val RESERVED_WORDS: Set = setOf( // keywords diff --git a/js/js.inliner/src/org/jetbrains/k2js/inline/JsInliner.java b/js/js.inliner/src/org/jetbrains/k2js/inline/JsInliner.java index 00101a65b73..e9e74d84372 100644 --- a/js/js.inliner/src/org/jetbrains/k2js/inline/JsInliner.java +++ b/js/js.inliner/src/org/jetbrains/k2js/inline/JsInliner.java @@ -33,6 +33,7 @@ import static org.jetbrains.k2js.inline.clean.CleanPackage.removeUnusedFunctionD import static org.jetbrains.k2js.inline.clean.CleanPackage.removeUnusedLocalFunctionDeclarations; import static org.jetbrains.k2js.inline.util.UtilPackage.IdentitySet; import static org.jetbrains.k2js.inline.util.UtilPackage.collectNamedFunctions; +import static org.jetbrains.k2js.inline.util.UtilPackage.refreshLabelNames; import static org.jetbrains.k2js.translate.utils.JsAstUtils.flattenStatement; public class JsInliner extends JsVisitorWithContextImpl { @@ -97,6 +98,8 @@ public class JsInliner extends JsVisitorWithContextImpl { @Override public void endVisit(JsFunction function, JsContext context) { super.endVisit(function, context); + refreshLabelNames(getInliningContext().newNamingContext(), function); + removeUnusedLocalFunctionDeclarations(function); processedFunctions.add(function); diff --git a/js/js.inliner/src/org/jetbrains/k2js/inline/util/collectors/NameCollector.kt b/js/js.inliner/src/org/jetbrains/k2js/inline/util/collectors/NameCollector.kt index a527ec97781..ac3902ab0d1 100644 --- a/js/js.inliner/src/org/jetbrains/k2js/inline/util/collectors/NameCollector.kt +++ b/js/js.inliner/src/org/jetbrains/k2js/inline/util/collectors/NameCollector.kt @@ -36,11 +36,6 @@ class NameCollector(private val scope: JsScope) : RecursiveJsVisitor() { override fun visitFunction(x: JsFunction?) { } - override fun visitLabel(label: JsLabel?) { - super.visitLabel(label) - addNameIfNeeded(label) - } - private fun addNameIfNeeded(hasName: HasName?) { val name = hasName?.getName() val ident = name?.getIdent() diff --git a/js/js.inliner/src/org/jetbrains/k2js/inline/util/namingUtils.kt b/js/js.inliner/src/org/jetbrains/k2js/inline/util/namingUtils.kt index 0df446b1d2f..1a51ed89368 100644 --- a/js/js.inliner/src/org/jetbrains/k2js/inline/util/namingUtils.kt +++ b/js/js.inliner/src/org/jetbrains/k2js/inline/util/namingUtils.kt @@ -27,6 +27,7 @@ import java.util.IdentityHashMap import kotlin.test.assertTrue import org.jetbrains.k2js.inline.context.NamingContext import org.jetbrains.k2js.inline.util.needToAlias +import org.jetbrains.k2js.inline.util.rewriters.LabelNameRefreshingVisitor public fun aliasArgumentsIfNeeded( context: NamingContext, @@ -70,4 +71,16 @@ public fun renameLocalNames( val freshName = context.getFreshName(name) context.replaceName(name, freshName.makeRef()) } +} + +public fun refreshLabelNames( + context: NamingContext, + function: JsFunction +) { + val scope = function.getScope() + if (scope !is JsFunctionScope) throw AssertionError("JsFunction is expected to have JsFunctionScope") + + val visitor = LabelNameRefreshingVisitor(context, scope) + visitor.accept(function.getBody()) + context.applyRenameTo(function) } \ No newline at end of file diff --git a/js/js.inliner/src/org/jetbrains/k2js/inline/util/rewriters/LabelNameRefreshingVisitor.kt b/js/js.inliner/src/org/jetbrains/k2js/inline/util/rewriters/LabelNameRefreshingVisitor.kt new file mode 100644 index 00000000000..5ea68363d35 --- /dev/null +++ b/js/js.inliner/src/org/jetbrains/k2js/inline/util/rewriters/LabelNameRefreshingVisitor.kt @@ -0,0 +1,46 @@ +/* + * Copyright 2010-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.k2js.inline.util.rewriters + +import com.google.dart.compiler.backend.js.ast.JsVisitorWithContextImpl +import com.google.dart.compiler.backend.js.ast.JsFunctionScope +import org.jetbrains.k2js.inline.context.NamingContext +import com.google.dart.compiler.backend.js.ast.JsFunction +import com.google.dart.compiler.backend.js.ast.JsContext +import com.google.dart.compiler.backend.js.ast.JsLabel +import com.google.dart.compiler.backend.js.ast.JsContinue +import com.google.dart.compiler.backend.js.ast.JsBreak + +class LabelNameRefreshingVisitor(val context: NamingContext, val functionScope: JsFunctionScope) : JsVisitorWithContextImpl() { + override fun visit(x: JsFunction?, ctx: JsContext?): Boolean = false + + override fun visit(x: JsLabel?, ctx: JsContext?): Boolean { + val labelName = x!!.getName() + val freshName = functionScope.enterLabel(labelName.getIdent()) + + if (freshName.getIdent() != labelName.getIdent()) { + context.replaceName(labelName, freshName.makeRef()) + } + + return super.visit(x, ctx) + } + + override fun endVisit(x: JsLabel?, ctx: JsContext?) { + super.endVisit(x, ctx) + functionScope.exitLabel() + } +} \ No newline at end of file diff --git a/js/js.translator/src/org/jetbrains/k2js/translate/expression/ExpressionVisitor.java b/js/js.translator/src/org/jetbrains/k2js/translate/expression/ExpressionVisitor.java index 719b5d661e4..5c4f48d5a82 100644 --- a/js/js.translator/src/org/jetbrains/k2js/translate/expression/ExpressionVisitor.java +++ b/js/js.translator/src/org/jetbrains/k2js/translate/expression/ExpressionVisitor.java @@ -293,9 +293,12 @@ public final class ExpressionVisitor extends TranslatorVisitor { assert baseExpression != null; JsScope scope = context.scope(); assert scope instanceof JsFunctionScope: "Labeled statement is unexpected outside of function scope"; - JsName name = ((JsFunctionScope) scope).declareNameUnsafe(getReferencedName(expression.getTargetLabel())); + JsFunctionScope functionScope = (JsFunctionScope) scope; + String labelIdent = getReferencedName(expression.getTargetLabel()); + JsName labelName = functionScope.enterLabel(labelIdent); JsStatement baseStatement = Translation.translateAsStatement(baseExpression, context); - return new JsLabel(name, baseStatement).source(expression); + functionScope.exitLabel(); + return new JsLabel(labelName, baseStatement).source(expression); } @Override @@ -369,11 +372,13 @@ public final class ExpressionVisitor extends TranslatorVisitor { if (labelElement == null) { return null; } - else { - JsName name = context.scope().findName(getReferencedName(labelElement)); - assert name != null; - return name.makeRef(); - } + + String labelIdent = getReferencedName(labelElement); + JsScope scope = context.scope(); + assert scope instanceof JsFunctionScope: "Labeled statement is unexpected outside of function scope"; + JsName labelName = ((JsFunctionScope) scope).findLabel(labelIdent); + assert labelName != null; + return labelName.makeRef(); } @Override