JS: when translating nested classes of local classes, capture closure variables via outermost local class. See KT-12566

This commit is contained in:
Alexey Andreev
2016-12-14 13:04:09 +03:00
parent b50bfaf071
commit 87f4e53544
5 changed files with 62 additions and 27 deletions
@@ -1,6 +1,3 @@
// IGNORE_BACKEND: JS
// Enable when KT-12566 gets fixed
fun box(): String { fun box(): String {
var log = "" var log = ""
@@ -11100,13 +11100,7 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest {
@TestMetadata("ownClosureOfInnerLocalClass.kt") @TestMetadata("ownClosureOfInnerLocalClass.kt")
public void testOwnClosureOfInnerLocalClass() throws Exception { public void testOwnClosureOfInnerLocalClass() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/localClasses/ownClosureOfInnerLocalClass.kt"); String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/localClasses/ownClosureOfInnerLocalClass.kt");
try { doTest(fileName);
doTest(fileName);
}
catch (Throwable ignore) {
return;
}
throw new AssertionError("Looks like this test can be unmuted. Remove IGNORE_BACKEND directive for that.");
} }
@TestMetadata("withclosure.kt") @TestMetadata("withclosure.kt")
@@ -166,6 +166,11 @@ public class TranslationContext {
return new TranslationContext(this, staticContext, dynamicContext, aliasingContext.inner(), usageTracker, descriptor); return new TranslationContext(this, staticContext, dynamicContext, aliasingContext.inner(), usageTracker, descriptor);
} }
@NotNull
public TranslationContext inner(@NotNull MemberDescriptor descriptor) {
return new TranslationContext(this, staticContext, dynamicContext, aliasingContext.inner(), usageTracker, descriptor);
}
@NotNull @NotNull
public TranslationContext innerBlock(@NotNull JsBlock block) { public TranslationContext innerBlock(@NotNull JsBlock block) {
return new TranslationContext(this, staticContext, dynamicContext.innerBlock(block), aliasingContext, usageTracker, return new TranslationContext(this, staticContext, dynamicContext.innerBlock(block), aliasingContext, usageTracker,
@@ -379,7 +384,7 @@ public class TranslationContext {
@Nullable @Nullable
public JsExpression getAliasForDescriptor(@NotNull DeclarationDescriptor descriptor) { public JsExpression getAliasForDescriptor(@NotNull DeclarationDescriptor descriptor) {
JsNameRef nameRef = captureIfNeedAndGetCapturedName(descriptor); JsExpression nameRef = captureIfNeedAndGetCapturedName(descriptor);
if (nameRef != null) { if (nameRef != null) {
return nameRef; return nameRef;
} }
@@ -459,15 +464,23 @@ public class TranslationContext {
} }
@Nullable @Nullable
private JsNameRef captureIfNeedAndGetCapturedName(DeclarationDescriptor descriptor) { private JsExpression captureIfNeedAndGetCapturedName(DeclarationDescriptor descriptor) {
if (usageTracker != null) { if (usageTracker != null) {
usageTracker.used(descriptor); usageTracker.used(descriptor);
JsName name = getNameForCapturedDescriptor(usageTracker, descriptor); JsName name = getNameForCapturedDescriptor(usageTracker, descriptor);
if (name != null) { if (name != null) {
JsNameRef result = name.makeRef(); JsExpression result;
if (shouldCaptureViaThis()) { if (shouldCaptureViaThis()) {
result.setQualifier(JsLiteral.THIS); result = JsLiteral.THIS;
int depth = getOuterLocalClassDepth();
for (int i = 0; i < depth; ++i) {
result = new JsNameRef(Namer.OUTER_FIELD_NAME, result);
}
result = new JsNameRef(name, result);
}
else {
result = name.makeRef();
} }
return result; return result;
} }
@@ -476,6 +489,25 @@ public class TranslationContext {
return null; return null;
} }
private int getOuterLocalClassDepth() {
if (usageTracker == null) return 0;
MemberDescriptor capturingDescriptor = usageTracker.getContainingDescriptor();
if (!(capturingDescriptor instanceof ClassDescriptor)) return 0;
ClassDescriptor capturingClassDescriptor = (ClassDescriptor) capturingDescriptor;
ClassDescriptor currentDescriptor = classDescriptor;
if (currentDescriptor == null) return 0;
int depth = 0;
while (currentDescriptor != capturingClassDescriptor) {
DeclarationDescriptor container = currentDescriptor.getContainingDeclaration();
if (!(container instanceof ClassDescriptor)) return 0;
currentDescriptor = (ClassDescriptor) container;
depth++;
}
return depth;
}
private boolean shouldCaptureViaThis() { private boolean shouldCaptureViaThis() {
if (declarationDescriptor == null) return false; if (declarationDescriptor == null) return false;
@@ -79,7 +79,7 @@ class ClassTranslator private constructor(
context.addDeclarationStatement(constructorFunction.makeStmt()) context.addDeclarationStatement(constructorFunction.makeStmt())
val enumInitFunction = if (descriptor.kind == ClassKind.ENUM_CLASS) createEnumInitFunction() else null val enumInitFunction = if (descriptor.kind == ClassKind.ENUM_CLASS) createEnumInitFunction() else null
val nonConstructorContext = context.innerWithUsageTracker(scope, descriptor) val nonConstructorContext = context.withUsageTrackerIfNecessary(scope, descriptor)
nonConstructorContext.startDeclaration() nonConstructorContext.startDeclaration()
val delegationTranslator = DelegationTranslator(classDeclaration, nonConstructorContext) val delegationTranslator = DelegationTranslator(classDeclaration, nonConstructorContext)
translatePropertiesAsConstructorParameters(nonConstructorContext) translatePropertiesAsConstructorParameters(nonConstructorContext)
@@ -93,7 +93,7 @@ class ClassTranslator private constructor(
addMetadataType() addMetadataType()
context.addClass(descriptor) context.addClass(descriptor)
addSuperclassReferences() addSuperclassReferences()
classDeclaration.getSecondaryConstructors().forEach { generateSecondaryConstructor(context, it) } classDeclaration.secondaryConstructors.forEach { generateSecondaryConstructor(context, it) }
generatedBridgeMethods() generatedBridgeMethods()
@@ -115,6 +115,18 @@ class ClassTranslator private constructor(
} }
} }
private fun TranslationContext.withUsageTrackerIfNecessary(scope: JsScope, innerDescriptor: MemberDescriptor): TranslationContext {
return if (isLocalClass) {
innerWithUsageTracker(scope, innerDescriptor)
}
else {
inner(innerDescriptor)
}
}
private val isLocalClass
get() = descriptor.containingDeclaration !is ClassOrPackageFragmentDescriptor
private fun translatePrimaryConstructor( private fun translatePrimaryConstructor(
constructorFunction: JsFunction, constructorFunction: JsFunction,
classContext: TranslationContext, classContext: TranslationContext,
@@ -303,10 +315,10 @@ class ClassTranslator private constructor(
for (constructor in sortedConstructors) { for (constructor in sortedConstructors) {
constructor.superCallGenerator() constructor.superCallGenerator()
val nonConstructorUsageTracker = nonConstructorContext.usageTracker()!! val nonConstructorUsageTracker = nonConstructorContext.usageTracker()
val usageTracker = constructor.context.usageTracker()!! val usageTracker = constructor.context.usageTracker()!!
val nonConstructorCapturedVars = nonConstructorUsageTracker.capturedDescriptors val nonConstructorCapturedVars = if (isLocalClass) nonConstructorUsageTracker!!.capturedDescriptors else setOf()
val constructorCapturedVars = usageTracker.capturedDescriptors val constructorCapturedVars = usageTracker.capturedDescriptors
val capturedVars = (nonConstructorCapturedVars + constructorCapturedVars).distinct() val capturedVars = (nonConstructorCapturedVars + constructorCapturedVars).distinct()
@@ -319,9 +331,9 @@ class ClassTranslator private constructor(
for (callSite in constructorCallSites) { for (callSite in constructorCallSites) {
val closureQualifier = callSite.context.getArgumentForClosureConstructor(classDescriptor.thisAsReceiverParameter) val closureQualifier = callSite.context.getArgumentForClosureConstructor(classDescriptor.thisAsReceiverParameter)
capturedVars.forEach { nonConstructorUsageTracker.used(it) } capturedVars.forEach { nonConstructorUsageTracker!!.used(it) }
val closureArgs = capturedVars.map { val closureArgs = capturedVars.map {
val name = nonConstructorUsageTracker.getNameForCapturedDescriptor(it)!! val name = nonConstructorUsageTracker!!.getNameForCapturedDescriptor(it)!!
JsAstUtils.pureFqn(name, closureQualifier) JsAstUtils.pureFqn(name, closureQualifier)
} }
callSite.invocationArgs.addAll(0, closureArgs) callSite.invocationArgs.addAll(0, closureArgs)
@@ -332,12 +344,12 @@ class ClassTranslator private constructor(
private fun addClosureParameters(constructor: ConstructorInfo, nonConstructorContext: TranslationContext) { private fun addClosureParameters(constructor: ConstructorInfo, nonConstructorContext: TranslationContext) {
val usageTracker = constructor.context.usageTracker()!! val usageTracker = constructor.context.usageTracker()!!
val capturedVars = context().getClassOrConstructorClosure(constructor.descriptor) ?: return val capturedVars = context().getClassOrConstructorClosure(constructor.descriptor) ?: return
val nonConstructorUsageTracker = nonConstructorContext.usageTracker()!! val nonConstructorUsageTracker = nonConstructorContext.usageTracker()
val function = constructor.function val function = constructor.function
val additionalStatements = mutableListOf<JsStatement>() val additionalStatements = mutableListOf<JsStatement>()
for ((i, capturedVar) in capturedVars.withIndex()) { for ((i, capturedVar) in capturedVars.withIndex()) {
val fieldName = nonConstructorUsageTracker.capturedDescriptorToJsName[capturedVar] val fieldName = nonConstructorUsageTracker?.capturedDescriptorToJsName?.get(capturedVar)
val name = usageTracker.capturedDescriptorToJsName[capturedVar] ?: fieldName!! val name = usageTracker.capturedDescriptorToJsName[capturedVar] ?: fieldName!!
function.parameters.add(i, JsParameter(name)) function.parameters.add(i, JsParameter(name))
@@ -192,11 +192,11 @@ public final class ClassInitializerTranslator extends AbstractTranslator {
List<DeclarationDescriptor> superclassClosure = context.getClassOrConstructorClosure(superDescriptor); List<DeclarationDescriptor> superclassClosure = context.getClassOrConstructorClosure(superDescriptor);
if (superclassClosure != null) { if (superclassClosure != null) {
UsageTracker tracker = context.usageTracker(); UsageTracker tracker = context.usageTracker();
assert tracker != null : "Closure exists, therefore UsageTracker must exist too. Translating constructor of " + if (tracker != null) {
classDescriptor; for (DeclarationDescriptor capturedValue : superclassClosure) {
for (DeclarationDescriptor capturedValue : superclassClosure) { tracker.used(capturedValue);
tracker.used(capturedValue); arguments.add(tracker.getCapturedDescriptorToJsName().get(capturedValue).makeRef());
arguments.add(tracker.getCapturedDescriptorToJsName().get(capturedValue).makeRef()); }
} }
} }