Support reflection on lambdas and function expressions
Write a special annotation containing the bytes for the Callable protobuf message and deserialize it at runtime properly #KT-9005 Fixed
This commit is contained in:
@@ -33,14 +33,22 @@ import org.jetbrains.kotlin.descriptors.*;
|
||||
import org.jetbrains.kotlin.descriptors.impl.SimpleFunctionDescriptorImpl;
|
||||
import org.jetbrains.kotlin.incremental.components.NoLookupLocation;
|
||||
import org.jetbrains.kotlin.load.java.JvmAbi;
|
||||
import org.jetbrains.kotlin.load.java.JvmAnnotationNames;
|
||||
import org.jetbrains.kotlin.load.kotlin.PackageClassUtils;
|
||||
import org.jetbrains.kotlin.psi.JetElement;
|
||||
import org.jetbrains.kotlin.resolve.BindingContext;
|
||||
import org.jetbrains.kotlin.resolve.DescriptorUtils;
|
||||
import org.jetbrains.kotlin.resolve.scopes.JetScope;
|
||||
import org.jetbrains.kotlin.serialization.DescriptorSerializer;
|
||||
import org.jetbrains.kotlin.serialization.ProtoBuf;
|
||||
import org.jetbrains.kotlin.serialization.SerializationUtil;
|
||||
import org.jetbrains.kotlin.serialization.StringTable;
|
||||
import org.jetbrains.kotlin.serialization.deserialization.NameResolver;
|
||||
import org.jetbrains.kotlin.serialization.jvm.BitEncoding;
|
||||
import org.jetbrains.kotlin.types.JetType;
|
||||
import org.jetbrains.kotlin.types.expressions.OperatorConventions;
|
||||
import org.jetbrains.kotlin.utils.UtilsPackage;
|
||||
import org.jetbrains.org.objectweb.asm.AnnotationVisitor;
|
||||
import org.jetbrains.org.objectweb.asm.MethodVisitor;
|
||||
import org.jetbrains.org.objectweb.asm.Type;
|
||||
import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter;
|
||||
@@ -219,6 +227,23 @@ public class ClosureCodegen extends MemberCodegen<JetElement> {
|
||||
@Override
|
||||
protected void generateKotlinAnnotation() {
|
||||
writeKotlinSyntheticClassAnnotation(v, syntheticClassKind);
|
||||
|
||||
DescriptorSerializer serializer =
|
||||
DescriptorSerializer.createTopLevel(new JvmSerializerExtension(v.getSerializationBindings(), typeMapper));
|
||||
|
||||
ProtoBuf.Callable callableProto = serializer.callableProto(funDescriptor).build();
|
||||
|
||||
StringTable strings = serializer.getStringTable();
|
||||
NameResolver nameResolver = new NameResolver(strings.serializeSimpleNames(), strings.serializeQualifiedNames());
|
||||
|
||||
AnnotationVisitor av = v.getVisitor().visitAnnotation(asmDescByFqNameWithoutInnerClasses(JvmAnnotationNames.KOTLIN_CALLABLE), true);
|
||||
av.visit(JvmAnnotationNames.ABI_VERSION_FIELD_NAME, JvmAbi.VERSION);
|
||||
AnnotationVisitor array = av.visitArray(JvmAnnotationNames.DATA_FIELD_NAME);
|
||||
for (String string : BitEncoding.encodeBytes(SerializationUtil.serializeCallableData(nameResolver, callableProto))) {
|
||||
array.visit(null, string);
|
||||
}
|
||||
array.visitEnd();
|
||||
av.visitEnd();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -54,6 +54,19 @@ public class SerializationUtil {
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static byte[] serializeCallableData(@NotNull NameResolver nameResolver, @NotNull ProtoBuf.Callable callableProto) {
|
||||
try {
|
||||
ByteArrayOutputStream result = new ByteArrayOutputStream();
|
||||
serializeNameResolver(result, nameResolver);
|
||||
callableProto.writeTo(result);
|
||||
return result.toByteArray();
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw UtilsPackage.rethrow(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static void serializeNameResolver(@NotNull OutputStream out, @NotNull NameResolver nameResolver) {
|
||||
serializeStringTable(out, nameResolver.getStringTable(), nameResolver.getQualifiedNameTable());
|
||||
}
|
||||
|
||||
Vendored
+50
@@ -0,0 +1,50 @@
|
||||
import kotlin.reflect.*
|
||||
import kotlin.reflect.jvm.*
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertNull
|
||||
|
||||
fun lambda() {
|
||||
val f = { x: Int, y: String? -> }
|
||||
|
||||
val g = f.reflect()!!
|
||||
|
||||
// TODO: maybe change this name
|
||||
assertEquals("<anonymous>", g.name)
|
||||
assertEquals(listOf("x", "y"), g.parameters.map { it.name })
|
||||
assertEquals(listOf(false, true), g.parameters.map { it.type.isMarkedNullable })
|
||||
}
|
||||
|
||||
fun funExpr() {
|
||||
val f = fun(x: Int, y: String?) {}
|
||||
|
||||
val g = f.reflect()!!
|
||||
|
||||
// TODO: maybe change this name
|
||||
assertEquals("<no name provided>", g.name)
|
||||
assertEquals(listOf("x", "y"), g.parameters.map { it.name })
|
||||
assertEquals(listOf(false, true), g.parameters.map { it.type.isMarkedNullable })
|
||||
}
|
||||
|
||||
fun extensionFunExpr() {
|
||||
val f = fun String.(): String = this
|
||||
|
||||
val g = f.reflect()!!
|
||||
|
||||
assertEquals(KParameter.Kind.EXTENSION_RECEIVER, g.parameters.single().kind)
|
||||
assertEquals(null, g.parameters.single().name)
|
||||
}
|
||||
|
||||
fun customFunction() {
|
||||
val f = object : Function<String> {}
|
||||
assertNull(f.reflect())
|
||||
}
|
||||
|
||||
fun box(): String {
|
||||
lambda()
|
||||
funExpr()
|
||||
extensionFunExpr()
|
||||
|
||||
customFunction()
|
||||
|
||||
return "OK"
|
||||
}
|
||||
+15
@@ -3421,6 +3421,21 @@ public class BlackBoxWithStdlibCodegenTestGenerated extends AbstractBlackBoxCode
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("compiler/testData/codegen/boxWithStdlib/reflection/lambdaClasses")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
public static class LambdaClasses extends AbstractBlackBoxCodegenTest {
|
||||
public void testAllFilesPresentInLambdaClasses() throws Exception {
|
||||
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/boxWithStdlib/reflection/lambdaClasses"), Pattern.compile("^(.+)\\.kt$"), true);
|
||||
}
|
||||
|
||||
@TestMetadata("parameterNamesAndNullability.kt")
|
||||
public void testParameterNamesAndNullability() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/boxWithStdlib/reflection/lambdaClasses/parameterNamesAndNullability.kt");
|
||||
doTestWithStdlib(fileName);
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("compiler/testData/codegen/boxWithStdlib/reflection/mapping")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
|
||||
Reference in New Issue
Block a user