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 {
var log = ""
@@ -11100,13 +11100,7 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest {
@TestMetadata("ownClosureOfInnerLocalClass.kt")
public void testOwnClosureOfInnerLocalClass() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/localClasses/ownClosureOfInnerLocalClass.kt");
try {
doTest(fileName);
}
catch (Throwable ignore) {
return;
}
throw new AssertionError("Looks like this test can be unmuted. Remove IGNORE_BACKEND directive for that.");
doTest(fileName);
}
@TestMetadata("withclosure.kt")
@@ -166,6 +166,11 @@ public class TranslationContext {
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
public TranslationContext innerBlock(@NotNull JsBlock block) {
return new TranslationContext(this, staticContext, dynamicContext.innerBlock(block), aliasingContext, usageTracker,
@@ -379,7 +384,7 @@ public class TranslationContext {
@Nullable
public JsExpression getAliasForDescriptor(@NotNull DeclarationDescriptor descriptor) {
JsNameRef nameRef = captureIfNeedAndGetCapturedName(descriptor);
JsExpression nameRef = captureIfNeedAndGetCapturedName(descriptor);
if (nameRef != null) {
return nameRef;
}
@@ -459,15 +464,23 @@ public class TranslationContext {
}
@Nullable
private JsNameRef captureIfNeedAndGetCapturedName(DeclarationDescriptor descriptor) {
private JsExpression captureIfNeedAndGetCapturedName(DeclarationDescriptor descriptor) {
if (usageTracker != null) {
usageTracker.used(descriptor);
JsName name = getNameForCapturedDescriptor(usageTracker, descriptor);
if (name != null) {
JsNameRef result = name.makeRef();
JsExpression result;
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;
}
@@ -476,6 +489,25 @@ public class TranslationContext {
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() {
if (declarationDescriptor == null) return false;
@@ -79,7 +79,7 @@ class ClassTranslator private constructor(
context.addDeclarationStatement(constructorFunction.makeStmt())
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()
val delegationTranslator = DelegationTranslator(classDeclaration, nonConstructorContext)
translatePropertiesAsConstructorParameters(nonConstructorContext)
@@ -93,7 +93,7 @@ class ClassTranslator private constructor(
addMetadataType()
context.addClass(descriptor)
addSuperclassReferences()
classDeclaration.getSecondaryConstructors().forEach { generateSecondaryConstructor(context, it) }
classDeclaration.secondaryConstructors.forEach { generateSecondaryConstructor(context, it) }
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(
constructorFunction: JsFunction,
classContext: TranslationContext,
@@ -303,10 +315,10 @@ class ClassTranslator private constructor(
for (constructor in sortedConstructors) {
constructor.superCallGenerator()
val nonConstructorUsageTracker = nonConstructorContext.usageTracker()!!
val nonConstructorUsageTracker = nonConstructorContext.usageTracker()
val usageTracker = constructor.context.usageTracker()!!
val nonConstructorCapturedVars = nonConstructorUsageTracker.capturedDescriptors
val nonConstructorCapturedVars = if (isLocalClass) nonConstructorUsageTracker!!.capturedDescriptors else setOf()
val constructorCapturedVars = usageTracker.capturedDescriptors
val capturedVars = (nonConstructorCapturedVars + constructorCapturedVars).distinct()
@@ -319,9 +331,9 @@ class ClassTranslator private constructor(
for (callSite in constructorCallSites) {
val closureQualifier = callSite.context.getArgumentForClosureConstructor(classDescriptor.thisAsReceiverParameter)
capturedVars.forEach { nonConstructorUsageTracker.used(it) }
capturedVars.forEach { nonConstructorUsageTracker!!.used(it) }
val closureArgs = capturedVars.map {
val name = nonConstructorUsageTracker.getNameForCapturedDescriptor(it)!!
val name = nonConstructorUsageTracker!!.getNameForCapturedDescriptor(it)!!
JsAstUtils.pureFqn(name, closureQualifier)
}
callSite.invocationArgs.addAll(0, closureArgs)
@@ -332,12 +344,12 @@ class ClassTranslator private constructor(
private fun addClosureParameters(constructor: ConstructorInfo, nonConstructorContext: TranslationContext) {
val usageTracker = constructor.context.usageTracker()!!
val capturedVars = context().getClassOrConstructorClosure(constructor.descriptor) ?: return
val nonConstructorUsageTracker = nonConstructorContext.usageTracker()!!
val nonConstructorUsageTracker = nonConstructorContext.usageTracker()
val function = constructor.function
val additionalStatements = mutableListOf<JsStatement>()
for ((i, capturedVar) in capturedVars.withIndex()) {
val fieldName = nonConstructorUsageTracker.capturedDescriptorToJsName[capturedVar]
val fieldName = nonConstructorUsageTracker?.capturedDescriptorToJsName?.get(capturedVar)
val name = usageTracker.capturedDescriptorToJsName[capturedVar] ?: fieldName!!
function.parameters.add(i, JsParameter(name))
@@ -192,11 +192,11 @@ public final class ClassInitializerTranslator extends AbstractTranslator {
List<DeclarationDescriptor> superclassClosure = context.getClassOrConstructorClosure(superDescriptor);
if (superclassClosure != null) {
UsageTracker tracker = context.usageTracker();
assert tracker != null : "Closure exists, therefore UsageTracker must exist too. Translating constructor of " +
classDescriptor;
for (DeclarationDescriptor capturedValue : superclassClosure) {
tracker.used(capturedValue);
arguments.add(tracker.getCapturedDescriptorToJsName().get(capturedValue).makeRef());
if (tracker != null) {
for (DeclarationDescriptor capturedValue : superclassClosure) {
tracker.used(capturedValue);
arguments.add(tracker.getCapturedDescriptorToJsName().get(capturedValue).makeRef());
}
}
}