JS backend: fixed the variable capturing in deeply nested functions.

(cherry picked from commit cee29a6)
This commit is contained in:
develar
2012-09-06 18:30:58 +04:00
committed by Zalim Bashorov
parent 84542872b8
commit 4a37d56a1b
12 changed files with 253 additions and 299 deletions
@@ -35,4 +35,8 @@ public final class ClosureTest extends SingleFileTranslationTest {
public void testClosureReferencingMember() throws Exception {
fooBoxTest();
}
public void testLocalParameterInLocalNamedFunction() throws Exception {
fooBoxTest();
}
}
@@ -21,7 +21,6 @@ import com.intellij.psi.PsiElement;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.jet.lang.descriptors.CallableDescriptor;
import org.jetbrains.jet.lang.descriptors.ClassDescriptor;
import org.jetbrains.jet.lang.descriptors.DeclarationDescriptor;
import org.jetbrains.jet.lang.descriptors.Named;
import org.jetbrains.jet.lang.psi.JetExpression;
@@ -237,7 +236,7 @@ public class TranslationContext {
}
public JsExpression getAliasForDescriptor(@NotNull DeclarationDescriptor descriptor) {
if (usageTracker != null && descriptor instanceof ClassDescriptor) {
if (usageTracker != null) {
usageTracker.triggerUsed(descriptor);
}
return aliasingContext.getAliasForDescriptor(descriptor);
@@ -16,23 +16,189 @@
package org.jetbrains.k2js.translate.context;
import org.jetbrains.jet.lang.descriptors.DeclarationDescriptor;
import com.intellij.util.Consumer;
import com.intellij.util.SmartList;
import com.intellij.util.containers.OrderedSet;
import gnu.trove.THashSet;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.jet.lang.descriptors.*;
import org.jetbrains.k2js.translate.utils.JsDescriptorUtils;
import java.util.List;
import java.util.Set;
public final class UsageTracker {
@Nullable
private final ClassDescriptor trackedClassDescriptor;
@NotNull
private final MemberDescriptor memberDescriptor;
@Nullable
private final UsageTracker parent;
@Nullable
private List<UsageTracker> children;
public class UsageTracker {
private final DeclarationDescriptor trackedDescriptor;
private boolean used;
@Nullable
private Set<CallableDescriptor> capturedVariables;
private ClassDescriptor outerClassDescriptor;
public UsageTracker(DeclarationDescriptor trackedDescriptor) {
this.trackedDescriptor = trackedDescriptor;
public UsageTracker(@NotNull MemberDescriptor memberDescriptor, @Nullable UsageTracker parent, @Nullable ClassDescriptor trackedClassDescriptor) {
this.memberDescriptor = memberDescriptor;
this.trackedClassDescriptor = trackedClassDescriptor;
this.parent = parent;
if (parent != null) {
parent.addChild(this);
}
}
public boolean isUsed() {
return used;
}
private void addChild(UsageTracker child) {
if (children == null) {
children = new SmartList<UsageTracker>();
}
children.add(child);
}
private void addCapturedMember(CallableDescriptor descriptor) {
if (capturedVariables == null) {
capturedVariables = new OrderedSet<CallableDescriptor>();
}
capturedVariables.add(descriptor);
}
public void triggerUsed(DeclarationDescriptor descriptor) {
if (trackedDescriptor == descriptor) {
used = true;
if ((descriptor instanceof PropertyDescriptor || descriptor instanceof PropertyAccessorDescriptor)) {
checkOuterClass(descriptor);
}
else if (descriptor instanceof VariableDescriptor) {
VariableDescriptor variableDescriptor = (VariableDescriptor) descriptor;
if ((capturedVariables == null || !capturedVariables.contains(variableDescriptor)) &&
!isAncestor(memberDescriptor, variableDescriptor)) {
addCapturedMember(variableDescriptor);
}
}
else if (descriptor instanceof SimpleFunctionDescriptor) {
checkOuterClass(descriptor);
DeclarationDescriptor containingDeclaration = descriptor.getContainingDeclaration();
// local named function
CallableDescriptor callableDescriptor = (CallableDescriptor) descriptor;
if (!JsDescriptorUtils.isExtension(callableDescriptor) &&
!(containingDeclaration instanceof ClassOrNamespaceDescriptor) &&
!isAncestor(memberDescriptor, descriptor)) {
addCapturedMember(callableDescriptor);
}
}
else if (descriptor instanceof ClassDescriptor) {
if (trackedClassDescriptor == descriptor) {
used = true;
}
else if (parent != null) {
UsageTracker p = parent;
do {
if (p.trackedClassDescriptor == descriptor) {
break;
}
}
while ((p = p.parent) != null);
}
}
}
private void checkOuterClass(DeclarationDescriptor descriptor) {
if (outerClassDescriptor == null) {
DeclarationDescriptor containingDeclaration = descriptor.getContainingDeclaration();
if (containingDeclaration instanceof ClassDescriptor) {
outerClassDescriptor = (ClassDescriptor) containingDeclaration;
}
}
}
@Nullable
public ClassDescriptor getOuterClassDescriptor() {
if (outerClassDescriptor == null && parent != null) {
UsageTracker p = parent;
do {
if (p.outerClassDescriptor != null) {
return p.outerClassDescriptor;
}
}
while ((p = p.parent) != null);
}
return outerClassDescriptor;
}
public void forEachCaptured(Consumer<CallableDescriptor> consumer) {
forEachCaptured(consumer, this, children == null ? null : new THashSet<CallableDescriptor>());
}
private boolean isOneOfTheMyParentsIsAncestor(VariableDescriptor parameterDescriptor, UsageTracker requestor) {
if (requestor == this) {
return false;
}
FunctionDescriptor paramOwner = (FunctionDescriptor) parameterDescriptor.getContainingDeclaration();
UsageTracker p = parent;
do {
if (p.memberDescriptor == paramOwner) {
return true;
}
}
while ((p = p.parent) != null && p != requestor);
return false;
}
private void forEachCaptured(Consumer<CallableDescriptor> consumer, UsageTracker requestor, @Nullable THashSet<CallableDescriptor> visited) {
if (capturedVariables != null) {
for (CallableDescriptor variable : capturedVariables) {
if (variable instanceof VariableDescriptor && isOneOfTheMyParentsIsAncestor((VariableDescriptor) variable, requestor)) {
continue;
}
if (visited == null || visited.add(variable)) {
consumer.consume(variable);
}
}
}
if (children != null) {
for (UsageTracker child : children) {
child.forEachCaptured(consumer, requestor, visited);
}
}
}
public boolean hasCaptured() {
if (capturedVariables != null) {
assert !capturedVariables.isEmpty();
return true;
}
if (children != null) {
for (UsageTracker child : children) {
if (child.hasCaptured()) {
return true;
}
}
}
return false;
}
// differs from DescriptorUtils - fails if reach NamespaceDescriptor
private static boolean isAncestor(
@NotNull DeclarationDescriptor ancestor,
@NotNull DeclarationDescriptor declarationDescriptor
) {
DeclarationDescriptor descriptor = declarationDescriptor.getContainingDeclaration();
while (descriptor != null && !(descriptor instanceof NamespaceDescriptor)) {
if (ancestor == descriptor) {
return true;
}
descriptor = descriptor.getContainingDeclaration();
}
return false;
}
}
@@ -17,70 +17,56 @@
package org.jetbrains.k2js.translate.expression;
import com.google.dart.compiler.backend.js.ast.*;
import com.intellij.util.Consumer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.jet.lang.descriptors.CallableDescriptor;
import org.jetbrains.jet.lang.descriptors.DeclarationDescriptor;
import org.jetbrains.jet.lang.descriptors.ValueParameterDescriptor;
import org.jetbrains.jet.lang.descriptors.VariableDescriptor;
import org.jetbrains.jet.lang.psi.JetElement;
import org.jetbrains.k2js.translate.context.TranslationContext;
import org.jetbrains.k2js.translate.utils.closure.ClosureContext;
import org.jetbrains.k2js.translate.utils.closure.ClosureUtils;
import java.util.Collections;
import java.util.List;
abstract class InnerDeclarationTranslator {
protected final ClosureContext closureContext;
protected final TranslationContext context;
protected final JsFunction fun;
public InnerDeclarationTranslator(@NotNull JetElement element,
@NotNull DeclarationDescriptor descriptor,
public InnerDeclarationTranslator(
@NotNull TranslationContext context,
@NotNull JsFunction fun) {
@NotNull JsFunction fun
) {
this.context = context;
closureContext = ClosureUtils.captureClosure(context.bindingContext(), element, descriptor);
this.fun = fun;
}
protected List<ValueParameterDescriptor> getValueParameters() {
return Collections.emptyList();
}
public JsExpression translate(@NotNull JsNameRef nameRef, @Nullable JsExpression self) {
if (closureContext.getDescriptors().isEmpty() && self == JsLiteral.NULL) {
boolean hasCaptured = context.usageTracker().hasCaptured();
if (!hasCaptured && self == JsLiteral.NULL) {
return createExpression(nameRef, self);
}
else {
JsInvocation invocation = createInvocation(nameRef, self);
addCapturedValueParameters(invocation);
return invocation;
JsInvocation invocation = createInvocation(nameRef, self);
if (hasCaptured) {
final List<JsExpression> expressions = invocation.getArguments();
context.usageTracker().forEachCaptured(new Consumer<CallableDescriptor>() {
@Override
public void consume(CallableDescriptor descriptor) {
JsName name;
if (descriptor instanceof VariableDescriptor) {
name = context.getNameForDescriptor(descriptor);
}
else {
name = ((JsNameRef) context.getAliasForDescriptor(descriptor)).getName();
assert name != null;
}
fun.getParameters().add(new JsParameter(name));
expressions.add(name.makeRef());
}
});
}
return invocation;
}
protected abstract JsExpression createExpression(@NotNull JsNameRef nameRef, @Nullable JsExpression self);
protected abstract JsInvocation createInvocation(@NotNull JsNameRef nameRef, @Nullable JsExpression self);
private void addCapturedValueParameters(JsInvocation bind) {
if (closureContext.getDescriptors().isEmpty()) {
return;
}
List<JsExpression> expressions = bind.getArguments();
for (CallableDescriptor descriptor : closureContext.getDescriptors()) {
JsName name;
if (descriptor instanceof VariableDescriptor) {
name = context.getNameForDescriptor(descriptor);
}
else {
name = ((JsNameRef) context.getAliasForDescriptor(descriptor)).getName();
assert name != null;
}
fun.getParameters().add(new JsParameter(name));
expressions.add(name.makeRef());
}
}
}
@@ -22,28 +22,20 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.jet.lang.descriptors.ClassDescriptor;
import org.jetbrains.jet.lang.descriptors.FunctionDescriptor;
import org.jetbrains.jet.lang.descriptors.ValueParameterDescriptor;
import org.jetbrains.jet.lang.psi.JetElement;
import org.jetbrains.k2js.translate.context.TranslationContext;
import java.util.List;
class InnerFunctionTranslator extends InnerDeclarationTranslator {
private final FunctionDescriptor descriptor;
public InnerFunctionTranslator(@NotNull JetElement element,
public InnerFunctionTranslator(
@NotNull FunctionDescriptor descriptor,
@NotNull TranslationContext context,
@NotNull JsFunction fun) {
super(element, descriptor, context, fun);
@NotNull JsFunction fun
) {
super(context, fun);
this.descriptor = descriptor;
}
@Override
protected List<ValueParameterDescriptor> getValueParameters() {
return descriptor.getValueParameters();
}
@SuppressWarnings("MethodOverloadsMethodOfSuperclass")
@NotNull
public JsExpression translate(@NotNull JsNameRef nameRef, @NotNull TranslationContext outerContext) {
@@ -62,7 +54,8 @@ class InnerFunctionTranslator extends InnerDeclarationTranslator {
@NotNull
private JsExpression getThis(TranslationContext outerContext) {
ClassDescriptor outerClassDescriptor = closureContext.outerClassDescriptor;
//noinspection ConstantConditions
ClassDescriptor outerClassDescriptor = context.usageTracker().getOuterClassDescriptor();
if (outerClassDescriptor != null && descriptor.getReceiverParameter() == null) {
return outerContext.getThisObject(outerClassDescriptor);
}
@@ -19,13 +19,14 @@ package org.jetbrains.k2js.translate.expression;
import com.google.dart.compiler.backend.js.ast.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.jet.lang.descriptors.ClassDescriptor;
import org.jetbrains.jet.lang.psi.JetElement;
import org.jetbrains.k2js.translate.context.TranslationContext;
class InnerObjectTranslator extends InnerDeclarationTranslator {
public InnerObjectTranslator(@NotNull JetElement element, @NotNull ClassDescriptor descriptor, @NotNull TranslationContext context, @NotNull JsFunction fun) {
super(element, descriptor, context, fun);
public InnerObjectTranslator(
@NotNull TranslationContext context,
@NotNull JsFunction fun
) {
super(context, fun);
}
@Override
@@ -97,21 +97,20 @@ public class LiteralFunctionTranslator {
if (receiverDescriptor == null) {
aliasingContext = aliasingContext.inner(outerClass, new JsNameRef("o", fun.getName().makeRef()));
}
funContext = rootContext.contextWithScope(fun, aliasingContext, new UsageTracker(outerClass));
}
else {
outerClass = null;
asInner = DescriptorUtils.isTopLevelDeclaration(descriptor);
funContext = rootContext.contextWithScope(fun, aliasingContext, outerContext.usageTracker());
}
funContext = rootContext.contextWithScope(fun, aliasingContext,
new UsageTracker(descriptor, outerContext.usageTracker(), outerClass));
fun.getBody().getStatements().addAll(translateFunctionBody(descriptor, declaration, funContext).getStatements());
InnerFunctionTranslator translator = null;
if (!asInner) {
translator = new InnerFunctionTranslator(declaration.asElement(), descriptor, funContext, fun);
translator = new InnerFunctionTranslator(descriptor, funContext, fun);
}
if (asInner) {
@@ -161,12 +160,13 @@ public class LiteralFunctionTranslator {
JsFunction fun = createFunction();
JsNameRef outerClassRef = fun.getScope().declareName("$this").makeRef();
TranslationContext funContext = rootContext
.contextWithScope(fun, rootContext.aliasingContext().inner(outerClass, outerClassRef), new UsageTracker(outerClass));
.contextWithScope(fun, rootContext.aliasingContext().inner(outerClass, outerClassRef), new UsageTracker(descriptor, null,
outerClass));
fun.getBody().getStatements().add(new JsReturn(classTranslator.translate(funContext)));
JetClassBody body = declaration.getBody();
assert body != null;
InnerObjectTranslator translator = new InnerObjectTranslator(body, descriptor, funContext, fun);
InnerObjectTranslator translator = new InnerObjectTranslator(funContext, fun);
return translator.translate(createReference(fun), funContext.usageTracker().isUsed() ? outerClassRef : null);
}
}
@@ -51,15 +51,15 @@ public final class ReferenceTranslator {
}
@NotNull
public static JsExpression translateAsLocalNameReference(@NotNull DeclarationDescriptor referencedDescriptor,
public static JsExpression translateAsLocalNameReference(@NotNull DeclarationDescriptor descriptor,
@NotNull TranslationContext context) {
if (referencedDescriptor instanceof FunctionDescriptor || referencedDescriptor instanceof VariableDescriptor) {
JsExpression alias = context.aliasingContext().getAliasForDescriptor(referencedDescriptor);
if (descriptor instanceof FunctionDescriptor || descriptor instanceof VariableDescriptor) {
JsExpression alias = context.getAliasForDescriptor(descriptor);
if (alias != null) {
return alias;
}
}
return context.getNameForDescriptor(referencedDescriptor).makeRef();
return context.getNameForDescriptor(descriptor).makeRef();
}
@NotNull
@@ -1,144 +0,0 @@
/*
* Copyright 2010-2013 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.translate.utils.closure;
import com.intellij.psi.PsiElement;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.jet.JetNodeTypes;
import org.jetbrains.jet.lang.descriptors.*;
import org.jetbrains.jet.lang.descriptors.impl.LocalVariableDescriptor;
import org.jetbrains.jet.lang.psi.*;
import org.jetbrains.jet.lang.resolve.BindingContext;
import org.jetbrains.jet.lang.resolve.BindingContextUtils;
import org.jetbrains.k2js.translate.utils.BindingUtils;
import org.jetbrains.k2js.translate.utils.JsDescriptorUtils;
class CaptureClosureVisitor extends JetTreeVisitor<ClosureContext> {
@NotNull
private final BindingContext bindingContext;
@NotNull
private final DeclarationDescriptor functionDescriptor;
/*package*/ CaptureClosureVisitor(@NotNull DeclarationDescriptor descriptor, @NotNull BindingContext bindingContext) {
this.bindingContext = bindingContext;
functionDescriptor = descriptor;
}
@Override
public Void visitJetElement(JetElement element, ClosureContext data) {
if (element instanceof JetValueArgument) {
JetExpression expression = ((JetValueArgument) element).getArgumentExpression();
if (expression != null) {
expression.accept(this, data);
}
}
else if (!(element instanceof JetNamedFunction)) {
return super.visitJetElement(element, data);
}
return null;
}
@Override
public Void visitSimpleNameExpression(@NotNull JetSimpleNameExpression expression,
@NotNull ClosureContext context) {
if (expression.getNode().getElementType() == JetNodeTypes.OPERATION_REFERENCE) {
return null;
}
DeclarationDescriptor descriptor = BindingUtils.getNullableDescriptorForReferenceExpression(bindingContext, expression);
if (!(descriptor instanceof VariableDescriptor)) {
if (descriptor instanceof SimpleFunctionDescriptor) {
checkSimpleFunction(context, (SimpleFunctionDescriptor) descriptor);
}
return null;
}
VariableDescriptor variableDescriptor = (VariableDescriptor) descriptor;
if (captured(variableDescriptor, context)) {
context.put(variableDescriptor);
}
return null;
}
private void checkSimpleFunction(ClosureContext context, @Nullable SimpleFunctionDescriptor descriptor) {
if (descriptor == null || descriptor.getName().isSpecial()) {
return;
}
checkOuterClassDescriptor(descriptor, context);
DeclarationDescriptor containingDeclaration = descriptor.getContainingDeclaration();
// local named function
if (!JsDescriptorUtils.isExtension(descriptor) && !(containingDeclaration instanceof ClassOrNamespaceDescriptor) && !isAncestor(functionDescriptor, descriptor)) {
context.put(descriptor);
}
}
private boolean captured(VariableDescriptor descriptor, ClosureContext context) {
if (descriptor instanceof PropertyDescriptor) {
checkOuterClassDescriptor(descriptor, context);
return false;
}
// is not working, try test life - init is captured, but this method returns false
//if (bindingContext.get(BindingContext.CAPTURED_IN_CLOSURE, descriptor) != Boolean.TRUE) {
// return false;
//}
if (isAncestor(functionDescriptor, descriptor)) {
return false;
}
if (descriptor instanceof LocalVariableDescriptor || descriptor instanceof ValueParameterDescriptor) {
return true;
}
PsiElement variableDeclaration = BindingContextUtils.descriptorToDeclaration(bindingContext, descriptor);
if (variableDeclaration == null) {
// "it" doesn't have declaration
return false;
}
return (!descriptor.isVar() && variableDeclaration instanceof JetProperty) ||
variableDeclaration.getNode().getElementType().equals(JetNodeTypes.LOOP_PARAMETER);
}
// differs from DescriptorUtils - fails if reach NamespaceDescriptor
public static boolean isAncestor(@NotNull DeclarationDescriptor ancestor,
@NotNull DeclarationDescriptor declarationDescriptor) {
DeclarationDescriptor descriptor = declarationDescriptor.getContainingDeclaration();
while (descriptor != null && !(descriptor instanceof NamespaceDescriptor)) {
if (ancestor == descriptor) {
return true;
}
descriptor = descriptor.getContainingDeclaration();
}
return false;
}
private static void checkOuterClassDescriptor(DeclarationDescriptor descriptor, ClosureContext context) {
if (context.outerClassDescriptor == null) {
DeclarationDescriptor containingDeclaration = descriptor.getContainingDeclaration();
if (containingDeclaration instanceof ClassDescriptor) {
context.outerClassDescriptor = (ClassDescriptor) containingDeclaration;
}
}
}
}
@@ -1,43 +0,0 @@
/*
* Copyright 2010-2013 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.translate.utils.closure;
import com.intellij.util.containers.OrderedSet;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.jet.lang.descriptors.CallableDescriptor;
import org.jetbrains.jet.lang.descriptors.ClassDescriptor;
import java.util.Collection;
import java.util.Set;
public final class ClosureContext {
@NotNull
private final Set<CallableDescriptor> descriptors = new OrderedSet<CallableDescriptor>();
@Nullable
public ClassDescriptor outerClassDescriptor;
/*package*/ void put(@NotNull CallableDescriptor descriptor) {
descriptors.add(descriptor);
}
@NotNull
public Collection<CallableDescriptor> getDescriptors() {
return descriptors;
}
}
@@ -1,35 +0,0 @@
/*
* Copyright 2010-2013 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.translate.utils.closure;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.jet.lang.descriptors.DeclarationDescriptor;
import org.jetbrains.jet.lang.psi.JetElement;
import org.jetbrains.jet.lang.resolve.BindingContext;
public final class ClosureUtils {
private ClosureUtils() {
}
@NotNull
public static ClosureContext captureClosure(@NotNull BindingContext bindingContext, @NotNull JetElement element, @NotNull DeclarationDescriptor descriptor) {
CaptureClosureVisitor captureClosureVisitor = new CaptureClosureVisitor(descriptor, bindingContext);
ClosureContext closureContext = new ClosureContext();
element.acceptChildren(captureClosureVisitor, closureContext);
return closureContext;
}
}
@@ -0,0 +1,27 @@
package foo
fun getLastFocused(callback:(window:Any?)->Unit) {
callback(null)
}
fun box(): Boolean {
var result = "no"
createTab(true) {
result = it
}
return result == "yes"
}
fun createTab(focusWindow:Boolean, callback:((String)->Unit)?) {
getLastFocused {
fun createTab() {
if (focusWindow && callback != null) {
callback("yes")
}
}
if (it == null) {
createTab()
}
}
}