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);
|
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")
|
@TestMetadata("extensionFun.kt")
|
||||||
public void testExtensionFun() throws Exception {
|
public void testExtensionFun() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/box/reified/extensionFun.kt");
|
String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/box/reified/extensionFun.kt");
|
||||||
|
|||||||
+52
-16
@@ -590,31 +590,54 @@ public class TranslationContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private JsExpression captureIfNeedAndGetCapturedName(DeclarationDescriptor descriptor) {
|
private JsExpression captureIfNeedAndGetCapturedName(@NotNull 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) return getCapturedReference(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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
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() {
|
private int getOuterLocalClassDepth() {
|
||||||
if (usageTracker == null) return 0;
|
if (usageTracker == null) return 0;
|
||||||
MemberDescriptor capturingDescriptor = usageTracker.getContainingDescriptor();
|
MemberDescriptor capturingDescriptor = usageTracker.getContainingDescriptor();
|
||||||
@@ -708,6 +731,19 @@ public class TranslationContext {
|
|||||||
return getNameForDescriptor(descriptor).makeRef();
|
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
|
@Nullable
|
||||||
public JsName getOuterClassReference(ClassDescriptor descriptor) {
|
public JsName getOuterClassReference(ClassDescriptor descriptor) {
|
||||||
DeclarationDescriptor container = descriptor.getContainingDeclaration();
|
DeclarationDescriptor container = descriptor.getContainingDeclaration();
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ class UsageTracker(
|
|||||||
) {
|
) {
|
||||||
|
|
||||||
private val captured = linkedMapOf<DeclarationDescriptor, JsName>()
|
private val captured = linkedMapOf<DeclarationDescriptor, JsName>()
|
||||||
|
private val capturedTypesImpl = mutableMapOf<TypeParameterDescriptor, JsName>()
|
||||||
|
|
||||||
// For readonly access from external places.
|
// For readonly access from external places.
|
||||||
val capturedDescriptorToJsName: Map<DeclarationDescriptor, JsName>
|
val capturedDescriptorToJsName: Map<DeclarationDescriptor, JsName>
|
||||||
@@ -42,6 +43,9 @@ class UsageTracker(
|
|||||||
val capturedDescriptors: Set<DeclarationDescriptor>
|
val capturedDescriptors: Set<DeclarationDescriptor>
|
||||||
get() = captured.keys
|
get() = captured.keys
|
||||||
|
|
||||||
|
val capturedTypes: Map<TypeParameterDescriptor, JsName>
|
||||||
|
get() = capturedTypesImpl
|
||||||
|
|
||||||
fun used(descriptor: DeclarationDescriptor) {
|
fun used(descriptor: DeclarationDescriptor) {
|
||||||
if (isCaptured(descriptor)) return
|
if (isCaptured(descriptor)) return
|
||||||
|
|
||||||
@@ -77,6 +81,11 @@ class UsageTracker(
|
|||||||
parent?.captureIfNeed(descriptor)
|
parent?.captureIfNeed(descriptor)
|
||||||
|
|
||||||
captured[descriptor] = descriptor.getJsNameForCapturedDescriptor()
|
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 {
|
private fun isInLocalDeclaration(): Boolean {
|
||||||
|
|||||||
+22
-5
@@ -389,9 +389,14 @@ 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.flatMap {
|
||||||
|
val result = mutableListOf<JsExpression>()
|
||||||
val name = nonConstructorUsageTracker!!.getNameForCapturedDescriptor(it)!!
|
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)
|
callSite.invocationArgs.addAll(0, closureArgs)
|
||||||
}
|
}
|
||||||
@@ -405,17 +410,29 @@ class ClassTranslator private constructor(
|
|||||||
|
|
||||||
val function = constructor.function
|
val function = constructor.function
|
||||||
val additionalStatements = mutableListOf<JsStatement>()
|
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 fieldName = nonConstructorUsageTracker?.capturedDescriptorToJsName?.get(capturedVar)
|
||||||
val name = usageTracker.capturedDescriptorToJsName[capturedVar] ?: fieldName!!
|
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) {
|
if (fieldName != null && constructor == primaryConstructor) {
|
||||||
val source = (constructor.descriptor as? DeclarationDescriptorWithSource)?.source
|
|
||||||
additionalStatements += JsAstUtils.defineSimpleProperty(fieldName, name.makeRef(), 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)
|
function.body.statements.addAll(0, additionalStatements)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+3
@@ -543,6 +543,9 @@ public final class ExpressionVisitor extends TranslatorVisitor<JsNode> {
|
|||||||
if (closure != null) {
|
if (closure != null) {
|
||||||
for (DeclarationDescriptor capturedValue : closure) {
|
for (DeclarationDescriptor capturedValue : closure) {
|
||||||
closureArgs.add(context.getArgumentForClosureConstructor(capturedValue));
|
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) {
|
if (capturedDescriptor is TypeParameterDescriptor && capturedDescriptor.isReified) {
|
||||||
// Preserve the usual order
|
// Preserve the usual order
|
||||||
additionalArgs = listOf(invokingContext.getNameForDescriptor(capturedDescriptor).makeRef()) + additionalArgs
|
additionalArgs = listOf(invokingContext.getCapturedTypeName(capturedDescriptor).makeRef()) + additionalArgs
|
||||||
additionalParams = listOf(JsParameter(context.getNameForDescriptor(capturedDescriptor))) + additionalParams
|
additionalParams = listOf(JsParameter(context.getCapturedTypeName(capturedDescriptor))) + additionalParams
|
||||||
}
|
}
|
||||||
|
|
||||||
if (capturedDescriptor is CallableDescriptor && isLocalInlineDeclaration(capturedDescriptor)) {
|
if (capturedDescriptor is CallableDescriptor && isLocalInlineDeclaration(capturedDescriptor)) {
|
||||||
|
|||||||
@@ -134,7 +134,8 @@ fun getReferenceToJsClass(type: KotlinType, context: TranslationContext): JsExpr
|
|||||||
|
|
||||||
context.usageTracker()?.used(classifierDescriptor)
|
context.usageTracker()?.used(classifierDescriptor)
|
||||||
|
|
||||||
context.getNameForDescriptor(classifierDescriptor).makeRef()
|
context.captureTypeIfNeedAndGetCapturedName(classifierDescriptor) ?:
|
||||||
|
context.getNameForDescriptor(classifierDescriptor).makeRef()
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
throw IllegalStateException("Can't get reference for $type")
|
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