KT-11960 Fix for case when class instantiates itself

This commit is contained in:
Alexey Andreev
2016-05-12 18:54:42 +03:00
parent a300dee61f
commit 170a671a1b
8 changed files with 217 additions and 20 deletions
@@ -0,0 +1,72 @@
// Enable for JVM backend when KT-8120 gets fixed
// TARGET_BACKEND: JS
fun box(): String {
val capturedInConstructor = 1
val capturedInBody = 10
class A(var x: Int) {
var y = 0
fun copy(): A {
val result = A(x)
result.y += capturedInBody
return result
}
init {
y += x + capturedInConstructor
}
}
val a = A(100).copy()
if (a.y != 111) return "fail1a: ${a.y}"
if (a.x != 100) return "fail1b: ${a.x}"
// This does not work in JS backend due to some unrelated issue with lambdas
/*
class B(var x: Int) {
var y = 0
fun copier(): () -> B = {
val result = B(x)
result.y += capturedInBody
result
}
init {
y += x + capturedInConstructor
}
}
val b = B(100).copier()()
if (b.y != 111) return "fail2a: ${b.y}"
if (b.x != 100) return "fail2b: ${b.x}"
*/
// It's pretty hard to implement this properly for now. Presumably, one needs to inject closure fields into local classes
// after the entire code gets generated (which is possible, but not easy, in JS and, I believe, nearly impossible in JVM).
/*
class C(var x: Int) {
var y = 0
inner class D() {
fun copyOuter(): C {
val result = C(x)
result.y += capturedInBody
return result
}
}
init {
y += x + capturedInConstructor
}
}
val c = C(100).D().copyOuter()
if (c.y != 111) return "fail3a: ${c.y}"
if (c.x != 100) return "fail3b: ${c.x}"
*/
return "OK"
}
@@ -13009,6 +13009,12 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
doTest(fileName);
}
@TestMetadata("localClassesWithSelfInstantiation.kt")
public void testLocalClassesWithSelfInstantiation() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/secondaryConstructors/localClassesWithSelfInstantiation.kt");
doTest(fileName);
}
@TestMetadata("superCallPrimary.kt")
public void testSuperCallPrimary() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/secondaryConstructors/superCallPrimary.kt");
@@ -161,6 +161,12 @@ public class SecondaryConstructorTestGenerated extends AbstractSecondaryConstruc
doTest(fileName);
}
@TestMetadata("localClassesWithSelfInstantiation.kt")
public void testLocalClassesWithSelfInstantiation() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/secondaryConstructors/localClassesWithSelfInstantiation.kt");
doTest(fileName);
}
@TestMetadata("superCallPrimary.kt")
public void testSuperCallPrimary() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/secondaryConstructors/superCallPrimary.kt");
@@ -204,20 +204,29 @@ object ConstructorCallCase : FunctionCallCase() {
override fun FunctionCallInfo.dispatchReceiver() = doTranslate { argsWithReceiver(dispatchReceiver!!) }
private inline fun FunctionCallInfo.doTranslate(getArguments: CallArgumentTranslator.ArgumentsInfo.() -> List<JsExpression>): JsExpression {
private inline fun FunctionCallInfo.doTranslate(
getArguments: CallArgumentTranslator.ArgumentsInfo.() -> List<JsExpression>
): JsExpression {
val fqName = context.getQualifiedReference(callableDescriptor)
val functionRef = if (isNative()) fqName else context.aliasOrValue(callableDescriptor) { fqName }
val arguments = argumentsInfo.getArguments()
val invocationArguments = mutableListOf<JsExpression>()
val constructorDescriptor = callableDescriptor as ConstructorDescriptor
val closure = context.getClassOrConstructorClosure(constructorDescriptor)
val closureArgs = closure?.map { context.getArgumentForClosureConstructor(it) } ?: emptyList()
if (constructorDescriptor.isPrimary || AnnotationsUtils.isNativeObject(constructorDescriptor)) {
return JsNew(functionRef, closureArgs + arguments)
if (context.isDeferred(constructorDescriptor)) {
context.deferConstructorCall(constructorDescriptor, invocationArguments)
}
else {
return JsInvocation(functionRef, closureArgs + arguments)
val closure = context.getClassOrConstructorClosure(constructorDescriptor)
invocationArguments += closure?.map { context.getArgumentForClosureConstructor(it) }.orEmpty()
}
invocationArguments += argumentsInfo.getArguments()
return if (constructorDescriptor.isPrimary || AnnotationsUtils.isNativeObject(constructorDescriptor)) {
JsNew(functionRef, invocationArguments)
}
else {
JsInvocation(functionRef, invocationArguments)
}
}
}
@@ -0,0 +1,38 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.js.translate.context
import com.google.dart.compiler.backend.js.ast.JsExpression
import org.jetbrains.kotlin.descriptors.ConstructorDescriptor
class DeferredCallSite(val constructor: ConstructorDescriptor, val invocationArgs: MutableList<JsExpression>)
@@ -23,6 +23,8 @@ import com.google.dart.compiler.backend.js.ast.metadata.HasMetadata;
import com.google.dart.compiler.backend.js.ast.metadata.MetadataProperties;
import com.intellij.openapi.util.Factory;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.HashMap;
import com.intellij.util.containers.HashSet;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.builtins.ReflectionTypes;
@@ -41,6 +43,7 @@ import org.jetbrains.kotlin.resolve.calls.util.FakeCallableDescriptorForObject;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static org.jetbrains.kotlin.js.translate.utils.AnnotationsUtils.*;
import static org.jetbrains.kotlin.js.translate.utils.JsAstUtils.fqnWithoutSideEffects;
@@ -98,7 +101,10 @@ public final class StaticContext {
private final Map<JsScope, JsFunction> scopeToFunction = Maps.newHashMap();
@NotNull
private final Map<MemberDescriptor, List<DeclarationDescriptor>> localClassesClosure = Maps.newHashMap();
private final Map<MemberDescriptor, List<DeclarationDescriptor>> classOrConstructorClosure = Maps.newHashMap();
@NotNull
private final Map<ClassDescriptor, List<DeferredCallSite>> deferredCallSites = new HashMap<ClassDescriptor, List<DeferredCallSite>>();
@NotNull
private final JsConfig config;
@@ -658,12 +664,17 @@ public final class StaticContext {
}
public void putClassOrConstructorClosure(@NotNull MemberDescriptor localClass, @NotNull List<DeclarationDescriptor> closure) {
localClassesClosure.put(localClass, Lists.newArrayList(closure));
classOrConstructorClosure.put(localClass, Lists.newArrayList(closure));
}
@Nullable
public List<DeclarationDescriptor> getClassOrConstructorClosure(@NotNull MemberDescriptor descriptor) {
List<DeclarationDescriptor> result = localClassesClosure.get(descriptor);
List<DeclarationDescriptor> result = classOrConstructorClosure.get(descriptor);
return result != null ? Lists.newArrayList(result) : null;
}
@NotNull
public Map<ClassDescriptor, List<DeferredCallSite>> getDeferredCallSites() {
return deferredCallSites;
}
}
@@ -18,6 +18,8 @@ package org.jetbrains.kotlin.js.translate.context;
import com.google.dart.compiler.backend.js.ast.*;
import com.intellij.psi.PsiElement;
import kotlin.Unit;
import kotlin.jvm.functions.Function1;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.builtins.ReflectionTypes;
@@ -32,9 +34,7 @@ import org.jetbrains.kotlin.resolve.BindingTrace;
import org.jetbrains.kotlin.resolve.DescriptorUtils;
import org.jetbrains.kotlin.resolve.scopes.receivers.ExtensionReceiver;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.*;
import static org.jetbrains.kotlin.js.translate.context.UsageTrackerKt.getNameForCapturedDescriptor;
import static org.jetbrains.kotlin.js.translate.utils.BindingUtils.getDescriptorForElement;
@@ -454,10 +454,13 @@ public class TranslationContext {
}
@Nullable
public List<DeclarationDescriptor> getClassOrConstructorClosure(@NotNull MemberDescriptor localClass) {
List<DeclarationDescriptor> result = staticContext.getClassOrConstructorClosure(localClass);
if (result == null && localClass instanceof ConstructorDescriptor && ((ConstructorDescriptor) localClass).isPrimary()) {
result = staticContext.getClassOrConstructorClosure((ClassDescriptor) localClass.getContainingDeclaration());
public List<DeclarationDescriptor> getClassOrConstructorClosure(@NotNull MemberDescriptor classOrConstructor) {
List<DeclarationDescriptor> result = staticContext.getClassOrConstructorClosure(classOrConstructor);
if (result == null &&
classOrConstructor instanceof ConstructorDescriptor &&
((ConstructorDescriptor) classOrConstructor).isPrimary()
) {
result = staticContext.getClassOrConstructorClosure((ClassDescriptor) classOrConstructor.getContainingDeclaration());
}
return result;
}
@@ -506,4 +509,37 @@ public class TranslationContext {
return staticContext.getScopeForDescriptor(descriptor).declareName(Namer.OUTER_FIELD_NAME);
}
public void startDeclaration() {
ClassDescriptor classDescriptor = this.classDescriptor;
if (classDescriptor != null && !(classDescriptor.getContainingDeclaration() instanceof ClassOrPackageFragmentDescriptor)) {
staticContext.getDeferredCallSites().put(classDescriptor, new ArrayList<DeferredCallSite>());
}
}
@NotNull
public List<DeferredCallSite> endDeclaration() {
List<DeferredCallSite> result = null;
ClassDescriptor classDescriptor = this.classDescriptor;
if (classDescriptor != null) {
result = staticContext.getDeferredCallSites().remove(classDescriptor);
}
if (result == null) {
result = Collections.emptyList();
}
return result;
}
public boolean isDeferred(@NotNull ConstructorDescriptor constructor) {
ClassDescriptor classDescriptor = constructor.getContainingDeclaration();
return staticContext.getDeferredCallSites().containsKey(classDescriptor);
}
public void deferConstructorCall(@NotNull ConstructorDescriptor constructor, @NotNull List<JsExpression> invocationArgs) {
ClassDescriptor classDescriptor = constructor.getContainingDeclaration();
List<DeferredCallSite> callSites = staticContext.getDeferredCallSites().get(classDescriptor);
if (callSites == null) throw new IllegalStateException("This method should be call only when `isDeferred` method " +
"reports true for given constructor: " + constructor);
callSites.add(new DeferredCallSite(constructor, invocationArgs));
}
}
@@ -88,6 +88,7 @@ class ClassTranslator private constructor(
invocationArguments += getSuperclassReferences(context)
val nonConstructorContext = context.innerWithUsageTracker(scope, descriptor)
nonConstructorContext.startDeclaration()
val delegationTranslator = DelegationTranslator(classDeclaration, nonConstructorContext)
translatePropertiesAsConstructorParameters(nonConstructorContext, properties)
val bodyVisitor = DeclarationBodyVisitor(properties, staticProperties, scope)
@@ -105,7 +106,7 @@ class ClassTranslator private constructor(
val dataClassGenerator = JsDataClassGenerator(classDeclaration, context, properties)
emitConstructors(nonConstructorContext)
emitConstructors(nonConstructorContext, nonConstructorContext.endDeclaration())
for (constructor in allConstructors) {
addClosureParameters(constructor, nonConstructorContext, dataClassGenerator)
}
@@ -240,10 +241,19 @@ class ClassTranslator private constructor(
return if (primary != null) sequenceOf(primary) + secondaryConstructors else secondaryConstructors.asSequence()
}
private fun emitConstructors(nonConstructorContext: TranslationContext) {
private fun emitConstructors(nonConstructorContext: TranslationContext, callSites: List<DeferredCallSite>) {
// Build map that maps constructor to all constructors called via this()
val constructorMap = allConstructors.map { it.descriptor to it }.toMap()
fun mapConstructor(constructor: ConstructorDescriptor) =
if (constructor.isPrimary) constructor.containingDeclaration else constructor
val callSiteMap = callSites
.map { mapConstructor(it.constructor) }.distinct()
.map { Pair(it, mutableListOf<DeferredCallSite>()) }
.toMap()
callSites.forEach { callSiteMap[mapConstructor(it.constructor)]!! += it }
val thisCalls = secondaryConstructors.map {
val set = mutableSetOf<ConstructorInfo>()
val descriptor = it.descriptor
@@ -273,6 +283,15 @@ class ClassTranslator private constructor(
val descriptor = constructor.descriptor
nonConstructorContext.putClassOrConstructorClosure(descriptor, capturedVars)
val constructorCallSites = callSiteMap[constructor.descriptor].orEmpty()
for (callSite in constructorCallSites) {
capturedVars.forEach { nonConstructorUsageTracker.used(it) }
val closureArgs = capturedVars
.map { nonConstructorUsageTracker.capturedDescriptorToJsName[it]!! }
.map { JsAstUtils.fqnWithoutSideEffects(it, JsLiteral.THIS) }
callSite.invocationArgs.addAll(0, closureArgs)
}
}
}