JS: fix capturing of reified type parameters when used as class literals

See KT-19891
This commit is contained in:
Alexey Andreev
2017-09-07 17:01:06 +03:00
parent 03ce6d859f
commit 206369c088
8 changed files with 136 additions and 24 deletions
@@ -7268,6 +7268,12 @@ public class BoxJsTestGenerated extends AbstractBoxJsTest {
doTest(fileName);
}
@TestMetadata("capture.kt")
public void testCapture() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/box/reified/capture.kt");
doTest(fileName);
}
@TestMetadata("extensionFun.kt")
public void testExtensionFun() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/box/reified/extensionFun.kt");
@@ -590,31 +590,54 @@ public class TranslationContext {
}
@Nullable
private JsExpression captureIfNeedAndGetCapturedName(DeclarationDescriptor descriptor) {
private JsExpression captureIfNeedAndGetCapturedName(@NotNull DeclarationDescriptor descriptor) {
if (usageTracker != null) {
usageTracker.used(descriptor);
JsName name = getNameForCapturedDescriptor(usageTracker, descriptor);
if (name != null) {
JsExpression result;
if (shouldCaptureViaThis()) {
result = new JsThisRef();
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;
}
if (name != null) return getCapturedReference(name);
}
return null;
}
@Nullable
public JsExpression captureTypeIfNeedAndGetCapturedName(@NotNull TypeParameterDescriptor descriptor) {
if (usageTracker == null) return null;
usageTracker.used(descriptor);
JsName name = usageTracker.getCapturedTypes().get(descriptor);
return name != null ? getCapturedReference(name) : null;
}
@NotNull
public JsName getCapturedTypeName(@NotNull TypeParameterDescriptor descriptor) {
JsName result = usageTracker != null ? usageTracker.getCapturedTypes().get(descriptor) : null;
if (result == null) {
result = getNameForDescriptor(descriptor);
}
return result;
}
@NotNull
private JsExpression getCapturedReference(@NotNull JsName name) {
JsExpression result;
if (shouldCaptureViaThis()) {
result = new JsThisRef();
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;
}
private int getOuterLocalClassDepth() {
if (usageTracker == null) return 0;
MemberDescriptor capturingDescriptor = usageTracker.getContainingDescriptor();
@@ -708,6 +731,19 @@ public class TranslationContext {
return getNameForDescriptor(descriptor).makeRef();
}
@NotNull
public JsExpression getTypeArgumentForClosureConstructor(@NotNull TypeParameterDescriptor descriptor) {
JsExpression captured = null;
if (usageTracker != null) {
JsName name = usageTracker.getCapturedTypes().get(descriptor);
if (name != null) {
captured = name.makeRef();
}
}
return captured != null ? captured : getNameForDescriptor(descriptor).makeRef();
}
@Nullable
public JsName getOuterClassReference(ClassDescriptor descriptor) {
DeclarationDescriptor container = descriptor.getContainingDeclaration();
@@ -34,6 +34,7 @@ class UsageTracker(
) {
private val captured = linkedMapOf<DeclarationDescriptor, JsName>()
private val capturedTypesImpl = mutableMapOf<TypeParameterDescriptor, JsName>()
// For readonly access from external places.
val capturedDescriptorToJsName: Map<DeclarationDescriptor, JsName>
@@ -42,6 +43,9 @@ class UsageTracker(
val capturedDescriptors: Set<DeclarationDescriptor>
get() = captured.keys
val capturedTypes: Map<TypeParameterDescriptor, JsName>
get() = capturedTypesImpl
fun used(descriptor: DeclarationDescriptor) {
if (isCaptured(descriptor)) return
@@ -77,6 +81,11 @@ class UsageTracker(
parent?.captureIfNeed(descriptor)
captured[descriptor] = descriptor.getJsNameForCapturedDescriptor()
if (descriptor is TypeParameterDescriptor && descriptor.containingDeclaration.original != containingDescriptor.original) {
val name = "typeClosure\$" + NameSuggestion.sanitizeName(descriptor.name.asString())
capturedTypesImpl[descriptor] = JsScope.declareTemporaryName(name)
}
}
private fun isInLocalDeclaration(): Boolean {
@@ -389,9 +389,14 @@ class ClassTranslator private constructor(
for (callSite in constructorCallSites) {
val closureQualifier = callSite.context.getArgumentForClosureConstructor(classDescriptor.thisAsReceiverParameter)
capturedVars.forEach { nonConstructorUsageTracker!!.used(it) }
val closureArgs = capturedVars.map {
val closureArgs = capturedVars.flatMap {
val result = mutableListOf<JsExpression>()
val name = nonConstructorUsageTracker!!.getNameForCapturedDescriptor(it)!!
JsAstUtils.pureFqn(name, closureQualifier)
result += JsAstUtils.pureFqn(name, closureQualifier)
if (it is TypeParameterDescriptor) {
result += JsAstUtils.pureFqn(nonConstructorUsageTracker.capturedTypes[it]!!, closureQualifier)
}
result
}
callSite.invocationArgs.addAll(0, closureArgs)
}
@@ -405,17 +410,29 @@ class ClassTranslator private constructor(
val function = constructor.function
val additionalStatements = mutableListOf<JsStatement>()
for ((i, capturedVar) in capturedVars.withIndex()) {
val additionalParameters = mutableListOf<JsParameter>()
for (capturedVar in capturedVars) {
val fieldName = nonConstructorUsageTracker?.capturedDescriptorToJsName?.get(capturedVar)
val name = usageTracker.capturedDescriptorToJsName[capturedVar] ?: fieldName!!
function.parameters.add(i, JsParameter(name))
additionalParameters += JsParameter(name)
val source = (constructor.descriptor as? DeclarationDescriptorWithSource)?.source
if (fieldName != null && constructor == primaryConstructor) {
val source = (constructor.descriptor as? DeclarationDescriptorWithSource)?.source
additionalStatements += JsAstUtils.defineSimpleProperty(fieldName, name.makeRef(), source)
}
if (capturedVar is TypeParameterDescriptor) {
val typeFieldName = nonConstructorUsageTracker?.capturedTypes?.get(capturedVar)
val typeName = usageTracker.capturedTypes[capturedVar] ?: typeFieldName!!
additionalParameters += JsParameter(typeName)
if (typeFieldName != null && constructor == primaryConstructor) {
additionalStatements += JsAstUtils.defineSimpleProperty(typeFieldName, typeName.makeRef(), source)
}
}
}
function.parameters.addAll(0, additionalParameters)
function.body.statements.addAll(0, additionalStatements)
}
@@ -543,6 +543,9 @@ public final class ExpressionVisitor extends TranslatorVisitor<JsNode> {
if (closure != null) {
for (DeclarationDescriptor capturedValue : closure) {
closureArgs.add(context.getArgumentForClosureConstructor(capturedValue));
if (capturedValue instanceof TypeParameterDescriptor) {
closureArgs.add(context.getTypeArgumentForClosureConstructor((TypeParameterDescriptor) capturedValue));
}
}
}
@@ -145,8 +145,8 @@ fun JsFunction.withCapturedParameters(
if (capturedDescriptor is TypeParameterDescriptor && capturedDescriptor.isReified) {
// Preserve the usual order
additionalArgs = listOf(invokingContext.getNameForDescriptor(capturedDescriptor).makeRef()) + additionalArgs
additionalParams = listOf(JsParameter(context.getNameForDescriptor(capturedDescriptor))) + additionalParams
additionalArgs = listOf(invokingContext.getCapturedTypeName(capturedDescriptor).makeRef()) + additionalArgs
additionalParams = listOf(JsParameter(context.getCapturedTypeName(capturedDescriptor))) + additionalParams
}
if (capturedDescriptor is CallableDescriptor && isLocalInlineDeclaration(capturedDescriptor)) {
@@ -134,7 +134,8 @@ fun getReferenceToJsClass(type: KotlinType, context: TranslationContext): JsExpr
context.usageTracker()?.used(classifierDescriptor)
context.getNameForDescriptor(classifierDescriptor).makeRef()
context.captureTypeIfNeedAndGetCapturedName(classifierDescriptor) ?:
context.getNameForDescriptor(classifierDescriptor).makeRef()
}
else -> {
throw IllegalStateException("Can't get reference for $type")
+40
View File
@@ -0,0 +1,40 @@
// EXPECTED_REACHABLE_NODES: 1014
interface I {
fun foo(): String?
}
interface J {
fun bar(x: Any): Boolean
}
inline fun <reified T> a(): I = object : I {
override fun foo(): String? = T::class.simpleName
}
inline fun <reified T> b(): J = object : J {
override fun bar(x: Any): Boolean = x is T
}
inline fun <reified T> c(): () -> String? = { T::class.simpleName }
inline fun <reified T> d(): (Any) -> Boolean = { it is T }
fun box(): String {
val r1 = a<C>().foo()
if (r1 != "C") return "fail1: $r1"
val r2 = b<C>()
if (!r2.bar(C()) || r2.bar(D())) return "fail2"
val r3 = c<C>()()
if (r3 != "C") return "fail3: $r3"
val r4 = d<C>()
if (!r4(C()) || r4(D())) return "fail4"
return "OK"
}
class C
class D