diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/BoxJsTestGenerated.java b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/BoxJsTestGenerated.java index b1a202f3c7c..918842c90f4 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/BoxJsTestGenerated.java +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/BoxJsTestGenerated.java @@ -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"); diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/TranslationContext.java b/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/TranslationContext.java index 0c9fc85298e..e3e1a583fbf 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/TranslationContext.java +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/TranslationContext.java @@ -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(); diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/UsageTracker.kt b/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/UsageTracker.kt index 3474d44dba2..35804b6cc59 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/UsageTracker.kt +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/UsageTracker.kt @@ -34,6 +34,7 @@ class UsageTracker( ) { private val captured = linkedMapOf() + private val capturedTypesImpl = mutableMapOf() // For readonly access from external places. val capturedDescriptorToJsName: Map @@ -42,6 +43,9 @@ class UsageTracker( val capturedDescriptors: Set get() = captured.keys + val capturedTypes: Map + 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 { diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/declaration/ClassTranslator.kt b/js/js.translator/src/org/jetbrains/kotlin/js/translate/declaration/ClassTranslator.kt index e84dfaaea63..458f664f813 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/declaration/ClassTranslator.kt +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/declaration/ClassTranslator.kt @@ -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() 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() - for ((i, capturedVar) in capturedVars.withIndex()) { + val additionalParameters = mutableListOf() + 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) } diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/ExpressionVisitor.java b/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/ExpressionVisitor.java index 65324bb84d6..38b0d9fbf53 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/ExpressionVisitor.java +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/ExpressionVisitor.java @@ -543,6 +543,9 @@ public final class ExpressionVisitor extends TranslatorVisitor { if (closure != null) { for (DeclarationDescriptor capturedValue : closure) { closureArgs.add(context.getArgumentForClosureConstructor(capturedValue)); + if (capturedValue instanceof TypeParameterDescriptor) { + closureArgs.add(context.getTypeArgumentForClosureConstructor((TypeParameterDescriptor) capturedValue)); + } } } diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/LiteralFunctionTranslator.kt b/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/LiteralFunctionTranslator.kt index bf0fcceaa1e..2a7e4797644 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/LiteralFunctionTranslator.kt +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/LiteralFunctionTranslator.kt @@ -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)) { diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/utils/utils.kt b/js/js.translator/src/org/jetbrains/kotlin/js/translate/utils/utils.kt index e244ef783cf..3f798c1cb21 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/utils/utils.kt +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/utils/utils.kt @@ -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") diff --git a/js/js.translator/testData/box/reified/capture.kt b/js/js.translator/testData/box/reified/capture.kt new file mode 100644 index 00000000000..93eaf801649 --- /dev/null +++ b/js/js.translator/testData/box/reified/capture.kt @@ -0,0 +1,40 @@ +// EXPECTED_REACHABLE_NODES: 1014 +interface I { + fun foo(): String? +} + +interface J { + fun bar(x: Any): Boolean +} + +inline fun a(): I = object : I { + override fun foo(): String? = T::class.simpleName +} + +inline fun b(): J = object : J { + override fun bar(x: Any): Boolean = x is T +} + +inline fun c(): () -> String? = { T::class.simpleName } + +inline fun d(): (Any) -> Boolean = { it is T } + +fun box(): String { + val r1 = a().foo() + if (r1 != "C") return "fail1: $r1" + + val r2 = b() + if (!r2.bar(C()) || r2.bar(D())) return "fail2" + + val r3 = c()() + if (r3 != "C") return "fail3: $r3" + + val r4 = d() + if (!r4(C()) || r4(D())) return "fail4" + + return "OK" +} + +class C + +class D