JS backend: fix recursive closure local function by inner function or lambda.
#KT-4257 fixed
This commit is contained in:
@@ -51,4 +51,21 @@ public final class ClosureTest extends SingleFileTranslationTest {
|
||||
public void testWrappedVariableInExtensionFun() throws Exception {
|
||||
fooBoxTest();
|
||||
}
|
||||
|
||||
public void testSimpleRecursion() throws Exception {
|
||||
checkFooBoxIsOk();
|
||||
}
|
||||
|
||||
public void testClosureFunctionByInnerFunction() throws Exception {
|
||||
checkFooBoxIsOk();
|
||||
}
|
||||
|
||||
public void testClosureLocalFunctionByInnerFunction() throws Exception {
|
||||
checkFooBoxIsOk();
|
||||
}
|
||||
|
||||
// TODO: fix
|
||||
public void igonre_testClosureLocalFunctionByInnerFunctionInConstrunctor() throws Exception {
|
||||
checkFooBoxIsOk();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@ public final class UsageTracker {
|
||||
capturedVariables.add(descriptor);
|
||||
}
|
||||
|
||||
public void triggerUsed(DeclarationDescriptor descriptor) {
|
||||
public void triggerUsed(@NotNull DeclarationDescriptor descriptor) {
|
||||
if ((descriptor instanceof PropertyDescriptor || descriptor instanceof PropertyAccessorDescriptor)) {
|
||||
checkOuterClass(descriptor);
|
||||
}
|
||||
@@ -165,6 +165,21 @@ public final class UsageTracker {
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isCaptured(@NotNull CallableDescriptor descriptor) {
|
||||
if (capturedVariables != null && capturedVariables.contains(descriptor)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (children != null) {
|
||||
for (UsageTracker child : children) {
|
||||
if (child.isCaptured(descriptor)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// differs from DescriptorUtils - fails if reach PackageFragmentDescriptor
|
||||
private static boolean isAncestor(
|
||||
@NotNull DeclarationDescriptor ancestor,
|
||||
|
||||
@@ -22,7 +22,6 @@ import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.jet.JetNodeTypes;
|
||||
import org.jetbrains.jet.lang.descriptors.DeclarationDescriptor;
|
||||
import org.jetbrains.jet.lang.descriptors.FunctionDescriptor;
|
||||
import org.jetbrains.jet.lang.descriptors.VariableDescriptor;
|
||||
import org.jetbrains.jet.lang.psi.*;
|
||||
import org.jetbrains.jet.lang.resolve.BindingContext;
|
||||
@@ -409,11 +408,7 @@ public final class ExpressionVisitor extends TranslatorVisitor<JsNode> {
|
||||
@Override
|
||||
@NotNull
|
||||
public JsNode visitNamedFunction(@NotNull JetNamedFunction expression, @NotNull TranslationContext context) {
|
||||
JsExpression alias = LiteralFunctionTranslator.translate(expression, context);
|
||||
FunctionDescriptor descriptor = getFunctionDescriptor(context.bindingContext(), expression);
|
||||
JsName name = context.scope().declareFreshName(descriptor.getName().asString());
|
||||
context.aliasingContext().registerAlias(descriptor, name.makeRef());
|
||||
return new JsVars(new JsVars.JsVar(name, alias)).source(expression);
|
||||
return LiteralFunctionTranslator.translateLocalNamedFunction(expression, context).source(expression);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+12
-13
@@ -21,7 +21,6 @@ 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.VariableDescriptor;
|
||||
import org.jetbrains.k2js.translate.context.TranslationContext;
|
||||
import org.jetbrains.k2js.translate.context.UsageTracker;
|
||||
|
||||
@@ -55,24 +54,24 @@ abstract class InnerDeclarationTranslator {
|
||||
usageTracker.forEachCaptured(new Consumer<CallableDescriptor>() {
|
||||
@Override
|
||||
public void consume(CallableDescriptor descriptor) {
|
||||
JsName name;
|
||||
if (descriptor instanceof VariableDescriptor) {
|
||||
name = context.getNameForDescriptor(descriptor);
|
||||
}
|
||||
else {
|
||||
JsExpression alias = context.getAliasForDescriptor(descriptor);
|
||||
assert alias != null : "Alias not found for captured descriptor: " + descriptor;
|
||||
name = ((JsNameRef) alias).getName();
|
||||
assert name != null : "Descriptor's alias don't have name: " + descriptor;
|
||||
}
|
||||
fun.getParameters().add(new JsParameter(name));
|
||||
invocationArguments.add(name.makeRef());
|
||||
fun.getParameters().add(new JsParameter(getParameterNameFor(descriptor)));
|
||||
invocationArguments.add(getParameterNameRefFor(descriptor));
|
||||
}
|
||||
});
|
||||
}
|
||||
return invocation;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
protected JsName getParameterNameFor(@NotNull CallableDescriptor descriptor) {
|
||||
return context.getNameForDescriptor(descriptor);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
protected JsNameRef getParameterNameRefFor(@NotNull CallableDescriptor descriptor) {
|
||||
return getParameterNameFor(descriptor).makeRef();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
protected abstract JsExpression createExpression(@NotNull JsNameRef nameRef, @Nullable JsExpression self);
|
||||
|
||||
|
||||
+17
-2
@@ -20,20 +20,24 @@ import com.google.dart.compiler.backend.js.ast.*;
|
||||
import com.intellij.util.SmartList;
|
||||
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.FunctionDescriptor;
|
||||
import org.jetbrains.k2js.translate.context.TranslationContext;
|
||||
|
||||
class InnerFunctionTranslator extends InnerDeclarationTranslator {
|
||||
private final FunctionDescriptor descriptor;
|
||||
@NotNull private final FunctionDescriptor descriptor;
|
||||
@Nullable private final JsNameRef funRef;
|
||||
|
||||
public InnerFunctionTranslator(
|
||||
@NotNull FunctionDescriptor descriptor,
|
||||
@NotNull TranslationContext context,
|
||||
@NotNull JsFunction fun
|
||||
@NotNull JsFunction fun,
|
||||
@Nullable JsNameRef funRef
|
||||
) {
|
||||
super(context, fun);
|
||||
this.descriptor = descriptor;
|
||||
this.funRef = funRef;
|
||||
}
|
||||
|
||||
@SuppressWarnings("MethodOverloadsMethodOfSuperclass")
|
||||
@@ -42,6 +46,17 @@ class InnerFunctionTranslator extends InnerDeclarationTranslator {
|
||||
return translate(nameRef, getThis(outerContext));
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
protected JsNameRef getParameterNameRefFor(@NotNull CallableDescriptor descriptor) {
|
||||
if (descriptor == this.descriptor) {
|
||||
assert funRef != null;
|
||||
return funRef;
|
||||
}
|
||||
|
||||
return super.getParameterNameRefFor(descriptor);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
protected JsExpression createExpression(@NotNull JsNameRef nameRef, @Nullable JsExpression self) {
|
||||
|
||||
+59
-5
@@ -17,6 +17,7 @@
|
||||
package org.jetbrains.k2js.translate.expression;
|
||||
|
||||
import com.google.dart.compiler.backend.js.ast.*;
|
||||
import com.intellij.util.SmartList;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.jet.lang.descriptors.ClassDescriptor;
|
||||
@@ -28,12 +29,12 @@ import org.jetbrains.jet.lang.psi.JetClassOrObject;
|
||||
import org.jetbrains.jet.lang.psi.JetDeclarationWithBody;
|
||||
import org.jetbrains.jet.lang.resolve.DescriptorUtils;
|
||||
import org.jetbrains.k2js.translate.LabelGenerator;
|
||||
import org.jetbrains.k2js.translate.context.AliasingContext;
|
||||
import org.jetbrains.k2js.translate.context.Namer;
|
||||
import org.jetbrains.k2js.translate.context.TranslationContext;
|
||||
import org.jetbrains.k2js.translate.context.UsageTracker;
|
||||
import org.jetbrains.k2js.translate.context.*;
|
||||
import org.jetbrains.k2js.translate.declaration.ClassTranslator;
|
||||
import org.jetbrains.k2js.translate.general.AbstractTranslator;
|
||||
import org.jetbrains.k2js.translate.utils.JsAstUtils;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.jetbrains.k2js.translate.utils.BindingUtils.getFunctionDescriptor;
|
||||
import static org.jetbrains.k2js.translate.utils.FunctionBodyTranslator.translateFunctionBody;
|
||||
@@ -41,6 +42,7 @@ import static org.jetbrains.k2js.translate.utils.JsDescriptorUtils.getExpectedRe
|
||||
|
||||
public class LiteralFunctionTranslator extends AbstractTranslator {
|
||||
private static final LabelGenerator FUNCTION_NAME_GENERATOR = new LabelGenerator('f');
|
||||
private static final String CAPTURED_VALUE_FIELD = "v";
|
||||
|
||||
private final JetDeclarationWithBody declaration;
|
||||
private final FunctionDescriptor descriptor;
|
||||
@@ -49,6 +51,7 @@ public class LiteralFunctionTranslator extends AbstractTranslator {
|
||||
private final boolean inConstructorOrTopLevel;
|
||||
private final ClassDescriptor outerClass;
|
||||
private final JsName receiverName;
|
||||
private JsNameRef tempRef = null;
|
||||
|
||||
private LiteralFunctionTranslator(@NotNull JetDeclarationWithBody declaration, @NotNull TranslationContext context) {
|
||||
super(context);
|
||||
@@ -115,7 +118,7 @@ public class LiteralFunctionTranslator extends AbstractTranslator {
|
||||
else {
|
||||
JsNameRef funReference = context().define(FUNCTION_NAME_GENERATOR.generate(), jsFunction);
|
||||
|
||||
InnerFunctionTranslator innerTranslator = new InnerFunctionTranslator(descriptor, functionContext, jsFunction);
|
||||
InnerFunctionTranslator innerTranslator = new InnerFunctionTranslator(descriptor, functionContext, jsFunction, tempRef);
|
||||
result = innerTranslator.translate(funReference, context());
|
||||
}
|
||||
|
||||
@@ -130,6 +133,52 @@ public class LiteralFunctionTranslator extends AbstractTranslator {
|
||||
return finish();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public JsVars translateLocalNamedFunction() {
|
||||
// Add ability to capture this named function.
|
||||
// Will be available like `foo.v` (for function `foo`)
|
||||
// Can not generate direct call because function may have some closures.
|
||||
JsName funName = functionContext.getNameForDescriptor(descriptor);
|
||||
JsNameRef alias = new JsNameRef(CAPTURED_VALUE_FIELD, funName.makeRef());
|
||||
functionContext.aliasingContext().registerAlias(descriptor, alias);
|
||||
|
||||
translateBody();
|
||||
|
||||
UsageTracker funTracker = functionContext.usageTracker();
|
||||
assert funTracker != null;
|
||||
boolean funIsCaptured = funTracker.isCaptured(descriptor);
|
||||
|
||||
// Create temporary variable name which will be contain reference to the function.
|
||||
JsName temp;
|
||||
if (funIsCaptured) {
|
||||
assert !inConstructorOrTopLevel : "A recursive closure in constructor is unsupported.";
|
||||
// Use `context()` because it should be created in the scope which contain call.
|
||||
temp = context().scope().declareTemporary();
|
||||
tempRef = temp.makeRef();
|
||||
}
|
||||
else {
|
||||
temp = null;
|
||||
}
|
||||
|
||||
JsExpression result = finish();
|
||||
|
||||
List<JsVars.JsVar> vars = new SmartList<JsVars.JsVar>();
|
||||
|
||||
if (funIsCaptured) {
|
||||
JsVars.JsVar tempVar = new JsVars.JsVar(temp, new JsObjectLiteral());
|
||||
vars.add(tempVar);
|
||||
|
||||
// Save `result` to the field of temporary variable if the function is captured.
|
||||
result = JsAstUtils.assignment(new JsNameRef(CAPTURED_VALUE_FIELD, temp.makeRef()), result);
|
||||
|
||||
}
|
||||
|
||||
JsVars.JsVar fun = new JsVars.JsVar(funName, result);
|
||||
vars.add(fun);
|
||||
|
||||
return new JsVars(vars, /*mulitline =*/ false);
|
||||
}
|
||||
|
||||
private static void addRegularParameters(
|
||||
@NotNull FunctionDescriptor descriptor,
|
||||
@NotNull JsFunction fun,
|
||||
@@ -142,6 +191,11 @@ public class LiteralFunctionTranslator extends AbstractTranslator {
|
||||
FunctionTranslator.addParameters(fun.getParameters(), descriptor, funContext);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static JsVars translateLocalNamedFunction(@NotNull JetDeclarationWithBody declaration, @NotNull TranslationContext outerContext) {
|
||||
return new LiteralFunctionTranslator(declaration, outerContext).translateLocalNamedFunction();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static JsExpression translate(@NotNull JetDeclarationWithBody declaration, @NotNull TranslationContext outerContext) {
|
||||
return new LiteralFunctionTranslator(declaration, outerContext).translate();
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* 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 foo
|
||||
|
||||
fun run<T>(f: () -> T) = f()
|
||||
|
||||
val r = "OK"
|
||||
|
||||
fun simple(s: String? = null): String {
|
||||
if (s != null) return s
|
||||
|
||||
return run {
|
||||
simple("OK")
|
||||
}
|
||||
}
|
||||
|
||||
val ok = "OK"
|
||||
fun withClosure(s: String? = null): String {
|
||||
if (s != null) return s
|
||||
|
||||
return ok + run {
|
||||
withClosure(ok)
|
||||
}
|
||||
}
|
||||
|
||||
fun box(): String {
|
||||
if (simple("OK") != "OK") return "failed on simple recursion"
|
||||
|
||||
if (withClosure() != ok + ok) return "failed when closure something"
|
||||
|
||||
return "OK"
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* 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 foo
|
||||
|
||||
fun run<T>(f: () -> T) = f()
|
||||
|
||||
fun box(): String {
|
||||
fun simple(s: String? = null): String {
|
||||
if (s != null) return s
|
||||
|
||||
return run {
|
||||
simple("OK")
|
||||
}
|
||||
}
|
||||
|
||||
if (simple("OK") != "OK") return "failed on simple recursion"
|
||||
|
||||
val ok = "OK"
|
||||
fun withClosure(s: String? = null): String {
|
||||
if (s != null) return s
|
||||
|
||||
return ok + run {
|
||||
withClosure(ok)
|
||||
}
|
||||
}
|
||||
|
||||
if (withClosure() != ok + ok) return "failed when closure something"
|
||||
|
||||
return "OK"
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* 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 foo
|
||||
|
||||
fun run<T>(f: () -> T) = f()
|
||||
|
||||
class Foo {
|
||||
val OK = "OK";
|
||||
var result: String = ""
|
||||
{
|
||||
fun bar(s: String? = null) {
|
||||
if (s != null) {
|
||||
result = s
|
||||
return
|
||||
}
|
||||
|
||||
run {
|
||||
bar(OK)
|
||||
}
|
||||
}
|
||||
bar();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fun box(): String {
|
||||
return Foo().result
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* 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 foo
|
||||
|
||||
fun bar(i: Int = 0): Int = if (i == 7) i else bar(i - 1)
|
||||
|
||||
fun box(): String {
|
||||
val a = bar(10)
|
||||
if (a != 7) return "bar(10) = $a, but expected 7"
|
||||
|
||||
fun boo(i: Int = 0): Int = if (i == 4) i else boo(i - 1)
|
||||
val b = boo(17)
|
||||
if (b != 4) return "boo(17) = $b, but expected 4"
|
||||
|
||||
fun f() = 1
|
||||
val v = 3
|
||||
fun baz(i: Int = 0): Int = if (i == v) f() + v else baz(i - 1)
|
||||
|
||||
val c = baz(10)
|
||||
if (c != 4) return "baz(10) = $c, but expected 4"
|
||||
|
||||
return "OK"
|
||||
}
|
||||
Reference in New Issue
Block a user