JS: fix capturing of reified type parameters when used as class literals
See KT-19891
This commit is contained in:
@@ -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");
|
||||
|
||||
+52
-16
@@ -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 {
|
||||
|
||||
+22
-5
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
+3
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -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")
|
||||
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user