From e30a22b77870208eeebc2d253816f862d2e5dd40 Mon Sep 17 00:00:00 2001 From: pTalanov Date: Tue, 13 Mar 2012 23:03:45 +0400 Subject: [PATCH] Add a hacky solution to loop parameter capture problem. --- .../k2js/test/semantics/ClosureTest.java | 33 +++++++++ .../expression/FunctionTranslator.java | 58 +++++++++++---- .../k2js/translate/utils/BindingUtils.java | 4 +- .../utils/closure/CaptureClosureVisitor.java | 70 +++++++++++++++++++ .../utils/closure/ClosureContext.java | 41 +++++++++++ .../translate/utils/closure/ClosureUtils.java | 38 ++++++++++ .../closure/cases/iteratingCallbacks.kt | 30 ++++++++ 7 files changed, 258 insertions(+), 16 deletions(-) create mode 100644 js/js.tests/test/org/jetbrains/k2js/test/semantics/ClosureTest.java create mode 100644 js/js.translator/src/org/jetbrains/k2js/translate/utils/closure/CaptureClosureVisitor.java create mode 100644 js/js.translator/src/org/jetbrains/k2js/translate/utils/closure/ClosureContext.java create mode 100644 js/js.translator/src/org/jetbrains/k2js/translate/utils/closure/ClosureUtils.java create mode 100644 js/js.translator/testFiles/closure/cases/iteratingCallbacks.kt diff --git a/js/js.tests/test/org/jetbrains/k2js/test/semantics/ClosureTest.java b/js/js.tests/test/org/jetbrains/k2js/test/semantics/ClosureTest.java new file mode 100644 index 00000000000..0beb3836ac0 --- /dev/null +++ b/js/js.tests/test/org/jetbrains/k2js/test/semantics/ClosureTest.java @@ -0,0 +1,33 @@ +/* + * Copyright 2000-2012 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.test.semantics; + +import org.jetbrains.k2js.test.SingleFileTranslationTest; + +/** + * @author Pavel Talanov + */ +public final class ClosureTest extends SingleFileTranslationTest { + + public ClosureTest() { + super("closure/"); + } + + public void testIteratingCallbacks() throws Exception { + checkFooBoxIsTrue("iteratingCallbacks.kt"); + } +} diff --git a/js/js.translator/src/org/jetbrains/k2js/translate/expression/FunctionTranslator.java b/js/js.translator/src/org/jetbrains/k2js/translate/expression/FunctionTranslator.java index 544510e7fff..a8402d82a8c 100644 --- a/js/js.translator/src/org/jetbrains/k2js/translate/expression/FunctionTranslator.java +++ b/js/js.translator/src/org/jetbrains/k2js/translate/expression/FunctionTranslator.java @@ -22,16 +22,20 @@ import com.google.dart.compiler.util.AstUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.jet.lang.descriptors.*; import org.jetbrains.jet.lang.psi.JetDeclarationWithBody; +import org.jetbrains.jet.lang.psi.JetElement; import org.jetbrains.jet.lang.psi.JetExpression; import org.jetbrains.jet.lang.psi.JetFunctionLiteralExpression; -import org.jetbrains.jet.lang.types.lang.JetStandardClasses; import org.jetbrains.jet.lang.types.JetType; +import org.jetbrains.jet.lang.types.lang.JetStandardClasses; import org.jetbrains.k2js.translate.context.Namer; import org.jetbrains.k2js.translate.context.TemporaryVariable; import org.jetbrains.k2js.translate.context.TranslationContext; import org.jetbrains.k2js.translate.general.AbstractTranslator; import org.jetbrains.k2js.translate.general.Translation; +import org.jetbrains.k2js.translate.reference.ReferenceTranslator; import org.jetbrains.k2js.translate.utils.DescriptorUtils; +import org.jetbrains.k2js.translate.utils.closure.ClosureContext; +import org.jetbrains.k2js.translate.utils.closure.ClosureUtils; import org.jetbrains.k2js.translate.utils.mutator.Mutator; import java.util.ArrayList; @@ -83,43 +87,69 @@ public final class FunctionTranslator extends AbstractTranslator { @NotNull public JsFunction translateAsLocalFunction() { JsName functionName = context().getNameForElement(functionDeclaration); - JsFunction function = generateFunctionObject(); - function.setName(functionName); - return function; + generateFunctionObject(); + functionObject.setName(functionName); + return functionObject; } @NotNull public JsPropertyInitializer translateAsMethod() { JsName functionName = context().getNameForElement(functionDeclaration); - JsFunction function = generateFunctionObject(); - return new JsPropertyInitializer(functionName.makeRef(), function); + generateFunctionObject(); + return new JsPropertyInitializer(functionName.makeRef(), functionObject); } @NotNull public JsExpression translateAsLiteral() { + return mayBeWrapInClosureCaptureExpression(doTranslateAsLiteral()); + } + + @NotNull + private JsExpression doTranslateAsLiteral() { assert getExpectedThisDescriptor(descriptor) == null; ClassDescriptor containingClass = getContainingClass(descriptor); if (containingClass == null) { - return generateFunctionObject(); + generateFunctionObject(); + return functionObject; } return generateFunctionObjectWithAliasedThisReference(containingClass); } @NotNull - private JsExpression generateFunctionObjectWithAliasedThisReference(@NotNull ClassDescriptor containingClass) { - TemporaryVariable aliasForThis = newAliasForThis(context(), containingClass); - JsFunction function = generateFunctionObject(); - removeAliasForThis(context(), containingClass); - return AstUtil.newSequence(aliasForThis.assignmentExpression(), function); + private JsExpression mayBeWrapInClosureCaptureExpression(@NotNull JsExpression wrappedExpression) { + ClosureContext closureContext = ClosureUtils.captureClosure(context(), (JetElement) functionDeclaration); + if (closureContext.getDescriptors().isEmpty()) { + return wrappedExpression; + } + return wrapInClosureCaptureExpression(wrappedExpression, closureContext); } @NotNull - private JsFunction generateFunctionObject() { + private JsExpression wrapInClosureCaptureExpression(@NotNull JsExpression wrappedExpression, + @NotNull ClosureContext closureContext) { + JsFunction dummyFunction = new JsFunction(context().jsScope()); + JsInvocation dummyFunctionInvocation = AstUtil.newInvocation(dummyFunction); + for (VariableDescriptor variableDescriptor : closureContext.getDescriptors()) { + dummyFunction.getParameters().add(new JsParameter(context().getNameForDescriptor(variableDescriptor))); + dummyFunctionInvocation.getArguments().add(ReferenceTranslator.translateAsLocalNameReference(variableDescriptor, context())); + } + dummyFunction.setBody(AstUtil.newBlock(new JsReturn(wrappedExpression))); + return dummyFunctionInvocation; + } + + @NotNull + private JsExpression generateFunctionObjectWithAliasedThisReference(@NotNull ClassDescriptor containingClass) { + TemporaryVariable aliasForThis = newAliasForThis(context(), containingClass); + generateFunctionObject(); + removeAliasForThis(context(), containingClass); + return AstUtil.newSequence(aliasForThis.assignmentExpression(), functionObject); + } + + private void generateFunctionObject() { setParameters(functionObject, translateParameters()); translateBody(); functionObject.setBody(functionBody); restoreContext(); - return functionObject; } private void restoreContext() { diff --git a/js/js.translator/src/org/jetbrains/k2js/translate/utils/BindingUtils.java b/js/js.translator/src/org/jetbrains/k2js/translate/utils/BindingUtils.java index 157a9083822..a9e2d0643c3 100644 --- a/js/js.translator/src/org/jetbrains/k2js/translate/utils/BindingUtils.java +++ b/js/js.translator/src/org/jetbrains/k2js/translate/utils/BindingUtils.java @@ -185,8 +185,8 @@ public final class BindingUtils { } @Nullable - private static DeclarationDescriptor getNullableDescriptorForReferenceExpression(@NotNull BindingContext context, - @NotNull JetReferenceExpression reference) { + public static DeclarationDescriptor getNullableDescriptorForReferenceExpression(@NotNull BindingContext context, + @NotNull JetReferenceExpression reference) { DeclarationDescriptor referencedDescriptor = context.get(BindingContext.REFERENCE_TARGET, reference); if (isVariableAsFunction(referencedDescriptor)) { assert referencedDescriptor != null; diff --git a/js/js.translator/src/org/jetbrains/k2js/translate/utils/closure/CaptureClosureVisitor.java b/js/js.translator/src/org/jetbrains/k2js/translate/utils/closure/CaptureClosureVisitor.java new file mode 100644 index 00000000000..d69ddb9f242 --- /dev/null +++ b/js/js.translator/src/org/jetbrains/k2js/translate/utils/closure/CaptureClosureVisitor.java @@ -0,0 +1,70 @@ +/* + * Copyright 2000-2012 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 com.intellij.psi.util.PsiTreeUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.jet.JetNodeTypes; +import org.jetbrains.jet.lang.descriptors.DeclarationDescriptor; +import org.jetbrains.jet.lang.descriptors.VariableDescriptor; +import org.jetbrains.jet.lang.psi.JetElement; +import org.jetbrains.jet.lang.psi.JetSimpleNameExpression; +import org.jetbrains.jet.lang.psi.JetTreeVisitor; +import org.jetbrains.jet.lang.resolve.BindingContext; +import org.jetbrains.k2js.translate.utils.BindingUtils; + +/** + * @author Pavel Talanov + */ +public class CaptureClosureVisitor extends JetTreeVisitor { + + @NotNull + private final BindingContext bindingContext; + @NotNull + private final JetElement functionElement; + + /*package*/ CaptureClosureVisitor(@NotNull JetElement functionElement, @NotNull BindingContext bindingContext) { + this.bindingContext = bindingContext; + this.functionElement = functionElement; + + } + + @Override + public Void visitSimpleNameExpression(@NotNull JetSimpleNameExpression expression, + @NotNull ClosureContext context) { + expression.acceptChildren(this, context); + DeclarationDescriptor descriptor = BindingUtils.getNullableDescriptorForReferenceExpression(bindingContext, expression); + if (!(descriptor instanceof VariableDescriptor)) { + return null; + } + VariableDescriptor variableDescriptor = (VariableDescriptor) descriptor; + PsiElement variableDeclaration = bindingContext.get(BindingContext.DESCRIPTOR_TO_DECLARATION, descriptor); + if (variableDeclaration == null) { + return null; + } + if (PsiTreeUtil.isAncestor(functionElement, variableDeclaration, false)) { + return null; + } + boolean isLoopParameter = variableDeclaration.getNode().getElementType().equals(JetNodeTypes.LOOP_PARAMETER); + if (isLoopParameter) { + context.put(variableDescriptor); + } + return null; + } + +} diff --git a/js/js.translator/src/org/jetbrains/k2js/translate/utils/closure/ClosureContext.java b/js/js.translator/src/org/jetbrains/k2js/translate/utils/closure/ClosureContext.java new file mode 100644 index 00000000000..e2fec87d2d9 --- /dev/null +++ b/js/js.translator/src/org/jetbrains/k2js/translate/utils/closure/ClosureContext.java @@ -0,0 +1,41 @@ +/* + * Copyright 2000-2012 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.google.common.collect.Lists; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.jet.lang.descriptors.VariableDescriptor; + +import java.util.List; + +/** + * @author Pavel Talanov + */ +public final class ClosureContext { + + @NotNull + private List descriptors = Lists.newArrayList(); + + /*package*/ void put(@NotNull VariableDescriptor descriptor) { + descriptors.add(descriptor); + } + + @NotNull + public List getDescriptors() { + return descriptors; + } +} diff --git a/js/js.translator/src/org/jetbrains/k2js/translate/utils/closure/ClosureUtils.java b/js/js.translator/src/org/jetbrains/k2js/translate/utils/closure/ClosureUtils.java new file mode 100644 index 00000000000..2781f42fbbe --- /dev/null +++ b/js/js.translator/src/org/jetbrains/k2js/translate/utils/closure/ClosureUtils.java @@ -0,0 +1,38 @@ +/* + * Copyright 2000-2012 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.psi.JetElement; +import org.jetbrains.k2js.translate.context.TranslationContext; + +/** + * @author Pavel Talanov + */ +public final class ClosureUtils { + + private ClosureUtils() { + } + + @NotNull + public static ClosureContext captureClosure(@NotNull TranslationContext context, @NotNull JetElement function) { + CaptureClosureVisitor captureClosureVisitor = new CaptureClosureVisitor(function, context.bindingContext()); + ClosureContext closureContext = new ClosureContext(); + function.accept(captureClosureVisitor, closureContext); + return closureContext; + } +} diff --git a/js/js.translator/testFiles/closure/cases/iteratingCallbacks.kt b/js/js.translator/testFiles/closure/cases/iteratingCallbacks.kt new file mode 100644 index 00000000000..5928ac14768 --- /dev/null +++ b/js/js.translator/testFiles/closure/cases/iteratingCallbacks.kt @@ -0,0 +1,30 @@ +package foo + +import java.util.* + +fun box() : Boolean { + val oneTwo = Array(2) { + it + 1 + } + val a = ArrayList<()->Int>() + for (i in oneTwo) { + for (j in 1..2) { + a.add({ + var res = 0 + for (t in 0..2) { + res += i * j + } + res + }) + } + } + var sum = 0 + for (f in a) { + sum += f() + } + return (sum == 27) +} + +fun main(args : Array) { + println(box()) +}