KT-11030 Fix referencing wrong field generated for captured variable when this captured variable referenced both from constructor and non-constructor method. Avoid duplicate generation of call to delegated constructor.

This commit is contained in:
Alexey Andreev
2016-03-30 16:40:41 +03:00
parent a5e0c70988
commit 4ac730e5ed
7 changed files with 83 additions and 40 deletions
@@ -0,0 +1,9 @@
class A(val f: () -> Int) {
constructor() : this({ 23 })
}
fun box(): String {
val result = A().f()
if (result != 23) return "fail: $result"
return "OK"
}
@@ -12868,6 +12868,12 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
doTest(fileName); doTest(fileName);
} }
@TestMetadata("delegatedThisWithLambda.kt")
public void testDelegatedThisWithLambda() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/secondaryConstructors/delegatedThisWithLambda.kt");
doTest(fileName);
}
@TestMetadata("delegationWithPrimary.kt") @TestMetadata("delegationWithPrimary.kt")
public void testDelegationWithPrimary() throws Exception { public void testDelegationWithPrimary() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/secondaryConstructors/delegationWithPrimary.kt"); String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/secondaryConstructors/delegationWithPrimary.kt");
@@ -167,4 +167,8 @@ public final class ClosureTest extends SingleFileTranslationTest {
public void testClosureThisAndClassObject() throws Exception { public void testClosureThisAndClassObject() throws Exception {
checkFooBoxIsOk(); checkFooBoxIsOk();
} }
public void testLocalConstructorAndMethod() throws Exception {
checkFooBoxIsOk();
}
} }
@@ -119,6 +119,12 @@ public class SecondaryConstructorTestGenerated extends AbstractSecondaryConstruc
doTest(fileName); doTest(fileName);
} }
@TestMetadata("delegatedThisWithLambda.kt")
public void testDelegatedThisWithLambda() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/secondaryConstructors/delegatedThisWithLambda.kt");
doTest(fileName);
}
@TestMetadata("delegationWithPrimary.kt") @TestMetadata("delegationWithPrimary.kt")
public void testDelegationWithPrimary() throws Exception { public void testDelegationWithPrimary() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/secondaryConstructors/delegationWithPrimary.kt"); String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/secondaryConstructors/delegationWithPrimary.kt");
@@ -453,7 +453,11 @@ public class TranslationContext {
@Nullable @Nullable
public List<DeclarationDescriptor> getLocalClassClosure(@NotNull MemberDescriptor localClass) { public List<DeclarationDescriptor> getLocalClassClosure(@NotNull MemberDescriptor localClass) {
return staticContext.getLocalClassClosure(localClass); List<DeclarationDescriptor> result = staticContext.getLocalClassClosure(localClass);
if (result == null && localClass instanceof ConstructorDescriptor && ((ConstructorDescriptor) localClass).isPrimary()) {
result = staticContext.getLocalClassClosure((ClassDescriptor) localClass.getContainingDeclaration());
}
return result;
} }
@NotNull @NotNull
@@ -109,15 +109,11 @@ class ClassTranslator private constructor(
val dataClassGenerator = JsDataClassGenerator(classDeclaration, context, properties) val dataClassGenerator = JsDataClassGenerator(classDeclaration, context, properties)
inflateClosure() emitConstructors(nonConstructorContext)
for (constructor in allConstructors) { for (constructor in allConstructors) {
addClosureParameters(constructor, nonConstructorContext, dataClassGenerator) addClosureParameters(constructor, nonConstructorContext, dataClassGenerator)
} }
for (constructor in allConstructors) {
constructor.superCallGenerator()
}
if (descriptor.isData) { if (descriptor.isData) {
dataClassGenerator.generate() dataClassGenerator.generate()
} }
@@ -147,7 +143,8 @@ class ClassTranslator private constructor(
private fun translatePrimaryConstructor(classContext: TranslationContext, delegationTranslator: DelegationTranslator) { private fun translatePrimaryConstructor(classContext: TranslationContext, delegationTranslator: DelegationTranslator) {
if (isTrait()) return if (isTrait()) return
var constructorContext = classContext.innerWithUsageTracker(classContext.scope(), descriptor) val scope = JsFunctionScope(classContext.scope(), "$descriptor: primary constructor")
var constructorContext = classContext.innerWithUsageTracker(scope, descriptor)
val initializer = ClassInitializerTranslator(classDeclaration, constructorContext).generateInitializeMethod(delegationTranslator) val initializer = ClassInitializerTranslator(classDeclaration, constructorContext).generateInitializeMethod(delegationTranslator)
invocationArguments += initializer invocationArguments += initializer
@@ -159,9 +156,9 @@ class ClassTranslator private constructor(
val constructorDescriptor = BindingUtils.getDescriptorForElement(classContext.bindingContext(), constructor) as ConstructorDescriptor val constructorDescriptor = BindingUtils.getDescriptorForElement(classContext.bindingContext(), constructor) as ConstructorDescriptor
val classDescriptor = constructorDescriptor.containingDeclaration val classDescriptor = constructorDescriptor.containingDeclaration
var context = classContext.innerWithUsageTracker(classContext.scope(), constructorDescriptor) val constructorScope = classContext.getScopeForDescriptor(constructorDescriptor)
var context = classContext.innerWithUsageTracker(constructorScope, constructorDescriptor)
val constructorScope = context.getScopeForDescriptor(constructorDescriptor)
val thisName = constructorScope.declareName(Namer.ANOTHER_THIS_PARAMETER_NAME) val thisName = constructorScope.declareName(Namer.ANOTHER_THIS_PARAMETER_NAME)
val thisNameRef = thisName.makeRef() val thisNameRef = thisName.makeRef()
val receiverDescriptor = classDescriptor.thisAsReceiverParameter val receiverDescriptor = classDescriptor.thisAsReceiverParameter
@@ -204,16 +201,10 @@ class ClassTranslator private constructor(
// Generate super/this call to insert to beginning of the function // Generate super/this call to insert to beginning of the function
val resolvedCall = BindingContextUtils.getDelegationConstructorCall(context.bindingContext(), constructorDescriptor) val resolvedCall = BindingContextUtils.getDelegationConstructorCall(context.bindingContext(), constructorDescriptor)
val delegationClassDescriptor = resolvedCall?.resultingDescriptor?.containingDeclaration val delegationClassDescriptor = resolvedCall?.resultingDescriptor?.containingDeclaration
val superCall = if (resolvedCall != null && !KotlinBuiltIns.isAny(delegationClassDescriptor!!)) {
CallTranslator.translate(context, resolvedCall)
}
else {
null
}
if (superCall != null) { if (resolvedCall != null && !KotlinBuiltIns.isAny(delegationClassDescriptor!!)) {
superCallGenerators += { superCallGenerators += {
it += CallTranslator.translate(context, resolvedCall!!).toInvocationWith(leadingArgs, thisNameRef).makeStmt() it += CallTranslator.translate(context, resolvedCall).toInvocationWith(leadingArgs, thisNameRef).makeStmt()
} }
} }
@@ -248,9 +239,9 @@ class ClassTranslator private constructor(
return if (primary != null) sequenceOf(primary) + secondaryConstructors else secondaryConstructors.asSequence() return if (primary != null) sequenceOf(primary) + secondaryConstructors else secondaryConstructors.asSequence()
} }
private fun inflateClosure() { private fun emitConstructors(nonConstructorContext: TranslationContext) {
// Build map that maps constructor to all constructors called via this() // Build map that maps constructor to all constructors called via this()
val constructorMap = allConstructors.map { Pair(it.descriptor, it) }.toMap() val constructorMap = allConstructors.map { it.descriptor to it }.toMap()
val primaryConstructor = this.primaryConstructor val primaryConstructor = this.primaryConstructor
val thisCalls = allConstructors.map { val thisCalls = allConstructors.map {
@@ -286,9 +277,27 @@ class ClassTranslator private constructor(
allConstructors.forEach(::sort) allConstructors.forEach(::sort)
// Inflate closure // Emit constructors
for (constructor in sortedConstructors) { for (constructor in sortedConstructors) {
constructor.superCallGenerator()
val nonConstructorUsageTracker = nonConstructorContext.usageTracker()!!
val usageTracker = constructor.context.usageTracker() ?: continue val usageTracker = constructor.context.usageTracker() ?: continue
val nonConstructorCapturedVars = nonConstructorUsageTracker.capturedDescriptors
val constructorCapturedVars = usageTracker.capturedDescriptors
val capturedVars = (nonConstructorCapturedVars.asSequence() +
constructorCapturedVars.asSequence().filter { it !in nonConstructorCapturedVars }).toList()
val descriptor = constructor.descriptor
nonConstructorContext.putLocalClassClosure(descriptor, capturedVars)
if (descriptor is ClassDescriptor) {
if (primaryConstructor != null) {
nonConstructorContext.putLocalClassClosure(primaryConstructor.descriptor, capturedVars)
}
}
for (thisCall in thisCalls[constructor].orEmpty()) { for (thisCall in thisCalls[constructor].orEmpty()) {
val callUsageTracker = thisCall.context.usageTracker() ?: continue val callUsageTracker = thisCall.context.usageTracker() ?: continue
callUsageTracker.capturedDescriptors.forEach { usageTracker.used(it) } callUsageTracker.capturedDescriptors.forEach { usageTracker.used(it) }
@@ -299,31 +308,18 @@ class ClassTranslator private constructor(
private fun addClosureParameters(constructor: ConstructorInfo, nonConstructorContext: TranslationContext, private fun addClosureParameters(constructor: ConstructorInfo, nonConstructorContext: TranslationContext,
dataClassGenerator: JsDataClassGenerator) { dataClassGenerator: JsDataClassGenerator) {
val usageTracker = constructor.context.usageTracker()!! val usageTracker = constructor.context.usageTracker()!!
val capturedVars = context().getLocalClassClosure(constructor.descriptor) ?: return
val nonConstructorUsageTracker = nonConstructorContext.usageTracker()!! val nonConstructorUsageTracker = nonConstructorContext.usageTracker()!!
val nonConstructorCapturedVars = nonConstructorUsageTracker.capturedDescriptors
val constructorCapturedVars = usageTracker.capturedDescriptors
val capturedVars = (nonConstructorCapturedVars.asSequence() +
constructorCapturedVars.asSequence().filter { it !in nonConstructorCapturedVars }).toList()
val descriptor = constructor.descriptor
nonConstructorContext.putLocalClassClosure(descriptor, capturedVars)
if (descriptor is ClassDescriptor) {
val primaryConstructor = descriptor.constructors.find { it.isPrimary }
if (primaryConstructor != null) {
nonConstructorContext.putLocalClassClosure(primaryConstructor, capturedVars)
}
}
val function = constructor.function val function = constructor.function
for ((i, capturedVar) in capturedVars.withIndex()) { for ((i, capturedVar) in capturedVars.withIndex()) {
val name = usageTracker.capturedDescriptorToJsName[capturedVar] ?: val fieldName = nonConstructorUsageTracker.capturedDescriptorToJsName[capturedVar]
nonConstructorUsageTracker.capturedDescriptorToJsName[capturedVar]!! val name = usageTracker.capturedDescriptorToJsName[capturedVar] ?: fieldName!!
function.parameters.add(i, JsParameter(name)) function.parameters.add(i, JsParameter(name))
if (capturedVar in nonConstructorUsageTracker.capturedDescriptors && constructor == primaryConstructor) { if (fieldName != null && constructor == primaryConstructor) {
function.body.statements.add(i, JsAstUtils.defineSimpleProperty(name.ident, name.makeRef())) function.body.statements.add(i, JsAstUtils.defineSimpleProperty(fieldName.ident, name.makeRef()))
dataClassGenerator.addClosureVariable(name) dataClassGenerator.addClosureVariable(fieldName)
} }
} }
} }
@@ -0,0 +1,18 @@
package foo
interface B {
fun result(): Int
}
class A(private val x: Int) {
fun test() = object : B {
val y = x + 1
override fun result() = x * 10 + y
}
}
fun box(): String {
assertEquals(23, A(2).test().result())
return "OK"
}