KT-11823 Generate proper argument for closure constructor for this as a free variable. Give some explanation for function that generates closure arguments as well as a clear name. Give explanation to some code in UsageTracker.isSingletonReceiver, augmented by a test case.

This commit is contained in:
Alexey Andreev
2016-04-21 14:57:39 +03:00
parent ab79deacbd
commit 1764000bf4
10 changed files with 98 additions and 11 deletions
@@ -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();
}
}
@@ -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<String> additionalJsFiles(@NotNull EcmaVersion ecmaVersion) {
@@ -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)
@@ -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();
}
@@ -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
@@ -524,7 +524,7 @@ public final class ExpressionVisitor extends TranslatorVisitor<JsNode> {
List<JsExpression> closureArgs = new ArrayList<JsExpression>();
if (closure != null) {
for (DeclarationDescriptor capturedValue : closure) {
closureArgs.add(context.getParameterNameRefForInvocation(capturedValue));
closureArgs.add(context.getArgumentForClosureConstructor(capturedValue));
}
}
@@ -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))
@@ -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"
}
@@ -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"
}
@@ -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"
}