JS: when translating nested classes of local classes, capture closure variables via outermost local class. See KT-12566
This commit is contained in:
@@ -1,6 +1,3 @@
|
|||||||
// IGNORE_BACKEND: JS
|
|
||||||
// Enable when KT-12566 gets fixed
|
|
||||||
|
|
||||||
fun box(): String {
|
fun box(): String {
|
||||||
var log = ""
|
var log = ""
|
||||||
|
|
||||||
|
|||||||
+1
-7
@@ -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")
|
||||||
|
|||||||
+36
-4
@@ -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;
|
||||||
|
|
||||||
|
|||||||
+20
-8
@@ -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))
|
||||||
|
|||||||
+5
-5
@@ -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());
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user