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