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:
Alexander Udalov
2015-09-02 12:01:24 +03:00
parent 73ae0751ee
commit df935f5bb7
11 changed files with 204 additions and 16 deletions
@@ -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());
}
@@ -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"
}
@@ -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)