diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/ReifiedTestGenerated.java b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/ReifiedTestGenerated.java index dc260b3afff..d6cd616a828 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/ReifiedTestGenerated.java +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/ReifiedTestGenerated.java @@ -41,6 +41,12 @@ public class ReifiedTestGenerated extends AbstractReifiedTest { doTest(fileName); } + @TestMetadata("extensionLambda.kt") + public void testExtensionLambda() throws Exception { + String fileName = JetTestUtils.navigationMetadata("js/js.translator/testData/reified/cases/extensionLambda.kt"); + doTest(fileName); + } + @TestMetadata("extensionMethod.kt") public void testExtensionMethod() throws Exception { String fileName = JetTestUtils.navigationMetadata("js/js.translator/testData/reified/cases/extensionMethod.kt"); @@ -83,6 +89,12 @@ public class ReifiedTestGenerated extends AbstractReifiedTest { doTest(fileName); } + @TestMetadata("lambda.kt") + public void testLambda() throws Exception { + String fileName = JetTestUtils.navigationMetadata("js/js.translator/testData/reified/cases/lambda.kt"); + doTest(fileName); + } + @TestMetadata("lambdaNameClash.kt") public void testLambdaNameClash() throws Exception { String fileName = JetTestUtils.navigationMetadata("js/js.translator/testData/reified/cases/lambdaNameClash.kt"); diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/Namer.java b/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/Namer.java index fd3afc3ebce..0f509f48576 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/Namer.java +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/Namer.java @@ -22,6 +22,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.kotlin.builtins.KotlinBuiltIns; import org.jetbrains.kotlin.descriptors.CallableDescriptor; import org.jetbrains.kotlin.descriptors.ClassDescriptor; +import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor; import org.jetbrains.kotlin.idea.JetLanguage; import org.jetbrains.kotlin.name.FqName; import org.jetbrains.kotlin.name.FqNameUnsafe; @@ -218,6 +219,11 @@ public final class Namer { return new JsNameRef(CAPTURED_VAR_FIELD, ref); } + @NotNull + public static String isInstanceSuggestedName(@NotNull TypeParameterDescriptor descriptor) { + return "is" + descriptor.getName().getIdentifier(); + } + @NotNull public static Namer newInstance(@NotNull JsScope rootScope) { return new Namer(rootScope); 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 b0d8e2dfe1b..60e449a9417 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 @@ -339,12 +339,10 @@ public class TranslationContext { @Nullable private JsNameRef captureIfNeedAndGetCapturedName(DeclarationDescriptor descriptor) { - if (usageTracker != null && descriptor instanceof CallableDescriptor) { - CallableDescriptor callableDescriptor = (CallableDescriptor) descriptor; + if (usageTracker != null) { + usageTracker.used(descriptor); - usageTracker.used(callableDescriptor); - - JsName name = getNameForCapturedDescriptor(usageTracker, callableDescriptor); + JsName name = getNameForCapturedDescriptor(usageTracker, descriptor); if (name != null) return name.makeRef(); } 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 1e7e69ca5e3..b785a09afb3 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 @@ -31,13 +31,13 @@ class UsageTracker( private val scope: JsFunctionScope ) { - private val captured = linkedMapOf() + private val captured = linkedMapOf() // For readonly access from external places. - val capturedDescriptorToJsName: Map + val capturedDescriptorToJsName: Map get() = captured - public fun used(descriptor: CallableDescriptor) { + public fun used(descriptor: DeclarationDescriptor) { if (isCaptured(descriptor)) return // local named function @@ -53,9 +53,12 @@ class UsageTracker( else if (descriptor is ReceiverParameterDescriptor) { captureIfNeed(descriptor) } + else if (descriptor is TypeParameterDescriptor && descriptor.isReified()) { + captureIfNeed(descriptor) + } } - private fun captureIfNeed(descriptor: CallableDescriptor?) { + private fun captureIfNeed(descriptor: DeclarationDescriptor?) { if (descriptor == null || isCaptured(descriptor) || isAncestor(containingDescriptor, descriptor, /* strict = */ true)) return parent?.captureIfNeed(descriptor) @@ -63,13 +66,18 @@ class UsageTracker( captured[descriptor] = descriptor.getJsNameForCapturedDescriptor() } - private fun CallableDescriptor.getJsNameForCapturedDescriptor(): JsName { - val suggestedName = if (this is ReceiverParameterDescriptor) this.getNameForCapturedReceiver() else getSuggestedName(this) + private fun DeclarationDescriptor.getJsNameForCapturedDescriptor(): JsName { + val suggestedName = when (this) { + is ReceiverParameterDescriptor -> this.getNameForCapturedReceiver() + is TypeParameterDescriptor -> Namer.isInstanceSuggestedName(this) + else -> getSuggestedName(this) + } + return scope.declareFreshName(suggestedName) } } -public fun UsageTracker.getNameForCapturedDescriptor(descriptor: CallableDescriptor): JsName? = capturedDescriptorToJsName.get(descriptor) +public fun UsageTracker.getNameForCapturedDescriptor(descriptor: DeclarationDescriptor): JsName? = capturedDescriptorToJsName.get(descriptor) public fun UsageTracker.hasCapturedExceptContaining(): Boolean { val hasNotCaptured = @@ -79,7 +87,7 @@ public fun UsageTracker.hasCapturedExceptContaining(): Boolean { return !hasNotCaptured } -public fun UsageTracker.isCaptured(descriptor: CallableDescriptor): Boolean = capturedDescriptorToJsName.containsKey(descriptor) +public fun UsageTracker.isCaptured(descriptor: DeclarationDescriptor): Boolean = capturedDescriptorToJsName.containsKey(descriptor) // NOTE: don't use from other places to avoid name clashes! So, it is not in Namer. private fun ReceiverParameterDescriptor.getNameForCapturedReceiver(): String { diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/FunctionTranslator.java b/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/FunctionTranslator.java index b001a0b7cde..a60983a1f54 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/FunctionTranslator.java +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/FunctionTranslator.java @@ -33,7 +33,9 @@ import org.jetbrains.kotlin.psi.JetFunctionLiteralExpression; import org.jetbrains.kotlin.resolve.DescriptorUtils; import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; import static org.jetbrains.kotlin.js.translate.reference.CallExpressionTranslator.shouldBeInlined; import static org.jetbrains.kotlin.js.translate.utils.BindingUtils.getFunctionDescriptor; @@ -50,7 +52,7 @@ public final class FunctionTranslator extends AbstractTranslator { } @NotNull - private final TranslationContext functionBodyContext; + private TranslationContext functionBodyContext; @NotNull private final JetDeclarationWithBody functionDeclaration; @Nullable @@ -122,15 +124,19 @@ public final class FunctionTranslator extends AbstractTranslator { @NotNull private List translateParameters() { List jsParameters = new SmartList(); + Map aliases = new HashMap(); for (TypeParameterDescriptor type : descriptor.getTypeParameters()) { if (type.isReified()) { - JsName typeName = context().getNameForDescriptor(type); - JsName paramName = functionObject.getScope().declareName("is" + typeName.getIdent()); + String suggestedName = Namer.isInstanceSuggestedName(type); + JsName paramName = functionObject.getScope().declareName(suggestedName); jsParameters.add(new JsParameter(paramName)); + aliases.put(type, paramName.makeRef()); } } + functionBodyContext = functionBodyContext.innerContextWithDescriptorsAliased(aliases); + if (extensionFunctionReceiverName == null && descriptor.getValueParameters().isEmpty()) { return jsParameters; } 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 124c3bd86c0..e96108d625c 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 @@ -66,13 +66,13 @@ class LiteralFunctionTranslator(context: TranslationContext) : AbstractTranslato fun JsFunction.withCapturedParameters(context: TranslationContext, invokingContext: TranslationContext, descriptor: MemberDescriptor): JsExpression { - fun getParameterNameRefForInvocation(callableDescriptor: CallableDescriptor): JsExpression { - val alias = invokingContext.getAliasForDescriptor(callableDescriptor) + fun getParameterNameRefForInvocation(descriptor: DeclarationDescriptor): JsExpression { + val alias = invokingContext.getAliasForDescriptor(descriptor) if (alias != null) return alias - if (callableDescriptor is ReceiverParameterDescriptor) return JsLiteral.THIS + if (descriptor is ReceiverParameterDescriptor) return JsLiteral.THIS - return invokingContext.getNameForDescriptor(callableDescriptor).makeRef() + return invokingContext.getNameForDescriptor(descriptor).makeRef() } val ref = invokingContext.define(descriptor, this) @@ -90,7 +90,7 @@ fun JsFunction.withCapturedParameters(context: TranslationContext, invokingConte var additionalArgs = listOf(capturedRef) var additionalParams = listOf(JsParameter(name)) - if (isLocalInlineDeclaration(capturedDescriptor)) { + if (capturedDescriptor is CallableDescriptor && isLocalInlineDeclaration(capturedDescriptor)) { val aliasRef = capturedRef as? JsNameRef val localFunAlias = aliasRef?.getStaticRef() as? JsExpression diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/PatternTranslator.java b/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/PatternTranslator.java index 1a14e4d56ea..a75b4f8d989 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/PatternTranslator.java +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/PatternTranslator.java @@ -34,8 +34,6 @@ import org.jetbrains.kotlin.psi.JetTypeReference; import org.jetbrains.kotlin.resolve.DescriptorUtils; import org.jetbrains.kotlin.types.JetType; -import java.util.List; - import static org.jetbrains.kotlin.js.translate.utils.BindingUtils.getTypeByReference; import static org.jetbrains.kotlin.js.translate.utils.JsAstUtils.*; import static org.jetbrains.kotlin.js.descriptorUtils.DescriptorUtilsPackage.getNameIfStandardType; @@ -134,28 +132,9 @@ public final class PatternTranslator extends AbstractTranslator { "Expected type parameter " + typeParameter + " to be contained in CallableDescriptor, actual: " + containingDeclaration.getClass(); - CallableDescriptor containingDescriptor = (CallableDescriptor) containingDeclaration; - int index = countReifiedTypesBefore(containingDescriptor.getTypeParameters(), typeParameter.getIndex()); - JsFunction containingFunction = context().getFunctionObject(containingDescriptor); - JsParameter isTypeFunParameter = containingFunction.getParameters().get(index); - return isTypeFunParameter.getName().makeRef(); - } - - private static int countReifiedTypesBefore( - @NotNull List typeParameters, - int typeParamIndex - ) { - int count = 0; - - for (TypeParameterDescriptor typeParameter : typeParameters) { - if (typeParameter.getIndex() >= typeParamIndex) break; - - if (typeParameter.isReified()) { - count++; - } - } - - return count; + JsExpression alias = context().getAliasForDescriptor(typeParameter); + assert alias != null: "No alias found for reified type parameter: " + typeParameter; + return alias; } @NotNull diff --git a/js/js.translator/testData/reified/cases/extensionLambda.kt b/js/js.translator/testData/reified/cases/extensionLambda.kt new file mode 100644 index 00000000000..493a1031c17 --- /dev/null +++ b/js/js.translator/testData/reified/cases/extensionLambda.kt @@ -0,0 +1,21 @@ +package foo + +class A +class B + +fun apply(x: T, fn: T.()->R): R = x.fn() + +inline fun test(x: Any, y: Any): Boolean = + x is T && apply(y) { this is R } + +fun box(): String { + val a = A() + val b = B() + + assertEquals(true, test(a, b), "test(a, b)") + assertEquals(false, test(a, a), "test(a, a)") + assertEquals(false, test(b, b), "test(b, b)") + assertEquals(false, test(b, a), "test(b, a)") + + return "OK" +} \ No newline at end of file diff --git a/js/js.translator/testData/reified/cases/lambda.kt b/js/js.translator/testData/reified/cases/lambda.kt new file mode 100644 index 00000000000..8516db4ede7 --- /dev/null +++ b/js/js.translator/testData/reified/cases/lambda.kt @@ -0,0 +1,39 @@ +package foo + +// CHECK_CALLED: doFilter +// CHECK_NOT_CALLED: filterIsInstance + +data class A(val x: Int) + +data class B(val x: Int) + +// filter from stdlib is not used, because it's important, +// that filter function is not inline. When lambda is +// not inlined and captures some local variable, +// the test crashes on runtime (it's expected behaviour). +fun Array.doFilter(fn: (T)->Boolean): List { + val filtered = arrayListOf() + + for (i in 0..lastIndex) { + val element = this[i] + + if (fn(element)) { + filtered.add(element) + } + } + + return filtered +} + +inline fun filterIsInstance(arrayOfAnys: Array): List { + return arrayOfAnys.doFilter { it is T }.map { it as T } +} + +fun box(): String { + val src: Array = arrayOf(A(1), B(2), A(3), B(4)) + + assertEquals(listOf(A(1), A(3)), filterIsInstance(src)) + assertEquals(listOf(B(2), B(4)), filterIsInstance(src)) + + return "OK" +}