diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/ClosureTest.java b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/ClosureTest.java index 53c638ecf6a..c57de186e69 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/ClosureTest.java +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/ClosureTest.java @@ -173,7 +173,7 @@ public final class ClosureTest extends SingleFileTranslationTest { } // TODO: tests UsageTracker but encounters issue somewhere else. Uncomment when issue with `this` generation gets fixed - // Presumably, KT-11823 + // Presumably, KT-11996 /*public void testDeepInnerClassInLocalClass() throws Exception { checkFooBoxIsOk(); } @@ -185,4 +185,8 @@ public final class ClosureTest extends SingleFileTranslationTest { public void testImplicitGenericReceiverInExtensionInLocalClass() throws Exception { checkFooBoxIsOk(); } + + public void testClosureThisInLambdaInsideObject() throws Exception { + checkFooBoxIsOk(); + } } diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/NestedTypesTest.java b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/NestedTypesTest.java index d78502f318f..c1f5c94bf3c 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/NestedTypesTest.java +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/NestedTypesTest.java @@ -78,6 +78,14 @@ public class NestedTypesTest extends SingleFileTranslationTest { checkFooBoxIsOk(); } + public void testImplicitOuterThisFromLambda() throws Exception { + checkFooBoxIsOk(); + } + + public void testImplicitOuterThisFromLocalClass() throws Exception { + checkFooBoxIsOk(); + } + @NotNull @Override protected List additionalJsFiles(@NotNull EcmaVersion ecmaVersion) { diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/callTranslator/FunctionCallCases.kt b/js/js.translator/src/org/jetbrains/kotlin/js/translate/callTranslator/FunctionCallCases.kt index d3adb34e230..068de5c1eed 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/callTranslator/FunctionCallCases.kt +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/callTranslator/FunctionCallCases.kt @@ -211,7 +211,7 @@ object ConstructorCallCase : FunctionCallCase() { val constructorDescriptor = callableDescriptor as ConstructorDescriptor val closure = context.getClassOrConstructorClosure(constructorDescriptor) - val closureArgs = closure?.map { context.getParameterNameRefForInvocation(it) } ?: emptyList() + val closureArgs = closure?.map { context.getArgumentForClosureConstructor(it) } ?: emptyList() if (constructorDescriptor.isPrimary || AnnotationsUtils.isNativeObject(constructorDescriptor)) { return JsNew(functionRef, closureArgs + arguments) diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/TranslationContext.java b/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/TranslationContext.java index e2077055b01..eae779bc1a1 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/TranslationContext.java +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/TranslationContext.java @@ -466,11 +466,38 @@ public class TranslationContext { return result; } + /** + * Gets an expression to pass to a constructor of a closure function. I.e. consider the case: + * + * ``` + * fun a(x) { + * fun b(y) = x + y + * return b + * } + * ``` + * + * Here, `x` is a free variable of `b`. Transform `a` into the following form: + * + * ``` + * fun a(x) { + * fun b0(x0) = { y -> x0 * y } + * return b0(x) + * } + * ``` + * + * This function generates arguments passed to newly generated `b0` closure, as well as for the similar case of local class and + * object expression. + * + * @param descriptor represents a free variable or, more generally, free declaration. + * @return expression to pass to a closure constructor. + */ @NotNull - public JsExpression getParameterNameRefForInvocation(@NotNull DeclarationDescriptor descriptor) { + public JsExpression getArgumentForClosureConstructor(@NotNull DeclarationDescriptor descriptor) { JsExpression alias = getAliasForDescriptor(descriptor); if (alias != null) return alias; - if (descriptor instanceof ReceiverParameterDescriptor) return JsLiteral.THIS; + if (descriptor instanceof ReceiverParameterDescriptor) { + return getDispatchReceiver((ReceiverParameterDescriptor) descriptor); + } return getNameForDescriptor(descriptor).makeRef(); } diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/UsageTracker.kt b/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/UsageTracker.kt index 06b0dfe67a6..66ba78fbb94 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/UsageTracker.kt +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/UsageTracker.kt @@ -134,11 +134,15 @@ class UsageTracker( val container = descriptor.containingDeclaration if (!DescriptorUtils.isObject(container)) return false - // This is workaround, since sometimes translator generates wrong expression for `this` expressions. - // Presumably, it's related to KT-11823 - // TODO: remove when issue gets fixed. - if (containingDescriptor == container) return false - + // This code is necessary for one use case. If we don't treat `O::this` as a free variable of lambda, we'll get + // `this` in generated JS. `this` is generated since it's placed in aliasing context for `O::this`, so we will get + // it instead of generating FQN. However, we can't refer to `this` from lambda, since `this` points not to an instance of `C`, + // but to lambda function itself. We avoid it by treating `O::this` as a free variable. + // Example is: + // + // object A(val x: Int) { + // fun foo() = { x } + // } if (containingDescriptor !is ClassDescriptor) { val containingClass = getParentOfType(containingDescriptor, ClassDescriptor::class.java, false) if (containingClass == container) return false diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/ExpressionVisitor.java b/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/ExpressionVisitor.java index 9931ed26c3e..170f3b8e048 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/ExpressionVisitor.java +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/ExpressionVisitor.java @@ -524,7 +524,7 @@ public final class ExpressionVisitor extends TranslatorVisitor { List closureArgs = new ArrayList(); if (closure != null) { for (DeclarationDescriptor capturedValue : closure) { - closureArgs.add(context.getParameterNameRefForInvocation(capturedValue)); + closureArgs.add(context.getArgumentForClosureConstructor(capturedValue)); } } diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/LiteralFunctionTranslator.kt b/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/LiteralFunctionTranslator.kt index 8bf640b2ac5..c605931e0ca 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/LiteralFunctionTranslator.kt +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/LiteralFunctionTranslator.kt @@ -78,7 +78,7 @@ fun JsFunction.withCapturedParameters(context: TranslationContext, invokingConte for ((capturedDescriptor, name) in tracker.capturedDescriptorToJsName) { if (capturedDescriptor == tracker.containingDescriptor) continue - val capturedRef = invokingContext.getParameterNameRefForInvocation(capturedDescriptor) + val capturedRef = invokingContext.getArgumentForClosureConstructor(capturedDescriptor) var additionalArgs = listOf(capturedRef) var additionalParams = listOf(JsParameter(name)) diff --git a/js/js.translator/testData/closure/cases/closureThisInLambdaInsideObject.kt b/js/js.translator/testData/closure/cases/closureThisInLambdaInsideObject.kt new file mode 100644 index 00000000000..b683db36a5d --- /dev/null +++ b/js/js.translator/testData/closure/cases/closureThisInLambdaInsideObject.kt @@ -0,0 +1,13 @@ +package foo + +object A { + val x = 23 + val y = { x } + fun foo() = { x } +} + +fun box(): String { + assertEquals(23, A.foo()()) + assertEquals(23, A.y()) + return "OK" +} \ No newline at end of file diff --git a/js/js.translator/testData/nestedTypes/cases/implicitOuterThisFromLambda.kt b/js/js.translator/testData/nestedTypes/cases/implicitOuterThisFromLambda.kt new file mode 100644 index 00000000000..3d6cc4e6acc --- /dev/null +++ b/js/js.translator/testData/nestedTypes/cases/implicitOuterThisFromLambda.kt @@ -0,0 +1,13 @@ +// See KT-11823 +package foo + +class Outer(val x: Int) { + inner class Inner() { + fun foo() = { x } + } +} + +fun box(): String { + assertEquals(23, Outer(23).Inner().foo()()) + return "OK" +} \ No newline at end of file diff --git a/js/js.translator/testData/nestedTypes/cases/implicitOuterThisFromLocalClass.kt b/js/js.translator/testData/nestedTypes/cases/implicitOuterThisFromLocalClass.kt new file mode 100644 index 00000000000..87f497f091d --- /dev/null +++ b/js/js.translator/testData/nestedTypes/cases/implicitOuterThisFromLocalClass.kt @@ -0,0 +1,18 @@ +// See KT-11823 +package foo + +class Outer(val x: Int) { + inner class Inner() { + fun foo(): Int { + class A { + fun bar() = x + } + return A().bar() + } + } +} + +fun box(): String { + assertEquals(23, Outer(23).Inner().foo()) + return "OK" +} \ No newline at end of file