Support reflection for local delegated properties

#KT-15222 Fixed
This commit is contained in:
Alexander Udalov
2017-06-01 19:42:59 +03:00
parent c444c4d10b
commit 616d575fb6
27 changed files with 559 additions and 48 deletions
@@ -175,8 +175,11 @@ class MultifileClassPartCodegen(
}
}
val serializer = DescriptorSerializer.createTopLevel(JvmSerializerExtension(v.serializationBindings, state))
val packageProto = serializer.packagePartProto(packageFragment.fqName, members).build()
val extension = JvmSerializerExtension(v.serializationBindings, state)
val serializer = DescriptorSerializer.createTopLevel(extension)
val builder = serializer.packagePartProto(packageFragment.fqName, members)
extension.serializeJvmPackage(builder, partType)
val packageProto = builder.build()
val extraFlags = if (shouldGeneratePartHierarchy) JvmAnnotationNames.METADATA_MULTIFILE_PARTS_INHERIT_FLAG else 0
@@ -119,9 +119,11 @@ public class PackagePartCodegen extends MemberCodegen<KtFile> {
}
}
DescriptorSerializer serializer =
DescriptorSerializer.createTopLevel(new JvmSerializerExtension(v.getSerializationBindings(), state));
ProtoBuf.Package packageProto = serializer.packagePartProto(element.getPackageFqName(), members).build();
JvmSerializerExtension extension = new JvmSerializerExtension(v.getSerializationBindings(), state);
DescriptorSerializer serializer = DescriptorSerializer.createTopLevel(extension);
ProtoBuf.Package.Builder builder = serializer.packagePartProto(element.getPackageFqName(), members);
extension.serializeJvmPackage(builder, packagePartType);
ProtoBuf.Package packageProto = builder.build();
WriteAnnotationUtilKt.writeKotlinMetadata(v, state, KotlinClassHeader.Kind.FILE_FACADE, 0, av -> {
writeAnnotationData(av, serializer, packageProto);
@@ -17,14 +17,25 @@
package org.jetbrains.kotlin.codegen
import org.jetbrains.kotlin.descriptors.*
import java.util.ArrayList
import org.jetbrains.kotlin.descriptors.impl.LocalVariableDescriptor
import org.jetbrains.kotlin.descriptors.impl.PropertyDescriptorImpl
import org.jetbrains.kotlin.descriptors.impl.PropertyGetterDescriptorImpl
import org.jetbrains.kotlin.descriptors.impl.PropertySetterDescriptorImpl
import org.jetbrains.kotlin.serialization.DescriptorSerializer
import java.util.*
/**
* Given a function descriptor, creates another function descriptor with type parameters copied from outer context(s).
* This is needed because once we're serializing this to a proto, there's no place to store information about external type parameters.
*/
fun createFreeFakeLambdaDescriptor(descriptor: FunctionDescriptor): FunctionDescriptor {
val builder = descriptor.newCopyBuilder()
return createFreeDescriptor(descriptor)
}
private fun <D : CallableMemberDescriptor> createFreeDescriptor(descriptor: D): D {
@Suppress("UNCHECKED_CAST")
val builder = descriptor.newCopyBuilder() as CallableMemberDescriptor.CopyBuilder<D>
val typeParameters = ArrayList<TypeParameterDescriptor>(0)
builder.setTypeParameters(typeParameters)
@@ -41,3 +52,32 @@ fun createFreeFakeLambdaDescriptor(descriptor: FunctionDescriptor): FunctionDesc
return if (typeParameters.isEmpty()) descriptor else builder.build()!!
}
/**
* Given a local delegated variable descriptor, creates a descriptor of a property that should be observed
* when using reflection on that local variable at runtime.
* Only members used by [DescriptorSerializer.propertyProto] are implemented correctly in this property descriptor.
*/
fun createFreeFakeLocalPropertyDescriptor(descriptor: LocalVariableDescriptor): PropertyDescriptor {
val property = PropertyDescriptorImpl.create(
descriptor.containingDeclaration, descriptor.annotations, Modality.FINAL, descriptor.visibility, descriptor.isVar,
descriptor.name, CallableMemberDescriptor.Kind.DECLARATION, descriptor.source, false, descriptor.isConst,
false, false, false, @Suppress("DEPRECATION") descriptor.isDelegated
)
property.setType(descriptor.type, descriptor.typeParameters, descriptor.dispatchReceiverParameter, descriptor.extensionReceiverParameter)
property.initialize(
descriptor.getter?.run {
PropertyGetterDescriptorImpl(property, annotations, modality, visibility, true, isExternal, isInline, kind, null, source).apply {
initialize(this@run.returnType)
}
},
descriptor.setter?.run {
PropertySetterDescriptorImpl(property, annotations, modality, visibility, true, isExternal, isInline, kind, null, source).apply {
initialize(this@run.valueParameters.single())
}
}
)
return createFreeDescriptor(property)
}
@@ -20,19 +20,22 @@ import com.intellij.openapi.util.Pair;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.codegen.ClassBuilderMode;
import org.jetbrains.kotlin.codegen.FakeDescriptorsForReferencesKt;
import org.jetbrains.kotlin.codegen.binding.CodegenBinding;
import org.jetbrains.kotlin.codegen.state.GenerationState;
import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper;
import org.jetbrains.kotlin.descriptors.*;
import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor;
import org.jetbrains.kotlin.descriptors.impl.LocalVariableDescriptor;
import org.jetbrains.kotlin.load.java.JvmAbi;
import org.jetbrains.kotlin.load.java.lazy.types.RawTypeImpl;
import org.jetbrains.kotlin.load.kotlin.JavaFlexibleTypeDeserializer;
import org.jetbrains.kotlin.load.kotlin.TypeSignatureMappingKt;
import org.jetbrains.kotlin.name.ClassId;
import org.jetbrains.kotlin.name.FqName;
import org.jetbrains.kotlin.serialization.AnnotationSerializer;
import org.jetbrains.kotlin.serialization.ProtoBuf;
import org.jetbrains.kotlin.serialization.SerializerExtension;
import org.jetbrains.kotlin.serialization.StringTable;
import org.jetbrains.kotlin.protobuf.GeneratedMessageLite;
import org.jetbrains.kotlin.resolve.BindingContext;
import org.jetbrains.kotlin.serialization.*;
import org.jetbrains.kotlin.serialization.jvm.ClassMapperLite;
import org.jetbrains.kotlin.serialization.jvm.JvmProtoBuf;
import org.jetbrains.kotlin.types.FlexibleType;
@@ -40,10 +43,14 @@ import org.jetbrains.kotlin.types.KotlinType;
import org.jetbrains.org.objectweb.asm.Type;
import org.jetbrains.org.objectweb.asm.commons.Method;
import java.util.List;
import static org.jetbrains.kotlin.codegen.serialization.JvmSerializationBindings.*;
public class JvmSerializerExtension extends SerializerExtension {
private final JvmSerializationBindings bindings;
private final BindingContext codegenBinding;
private final KotlinTypeMapper typeMapper;
private final StringTable stringTable;
private final AnnotationSerializer annotationSerializer;
private final boolean useTypeTable;
@@ -52,7 +59,9 @@ public class JvmSerializerExtension extends SerializerExtension {
public JvmSerializerExtension(@NotNull JvmSerializationBindings bindings, @NotNull GenerationState state) {
this.bindings = bindings;
this.stringTable = new JvmStringTable(state.getTypeMapper());
this.codegenBinding = state.getBindingContext();
this.typeMapper = state.getTypeMapper();
this.stringTable = new JvmStringTable(typeMapper);
this.annotationSerializer = new AnnotationSerializer(stringTable);
this.useTypeTable = state.getUseTypeTableInSerializer();
this.moduleName = state.getModuleName();
@@ -75,6 +84,8 @@ public class JvmSerializerExtension extends SerializerExtension {
if (!moduleName.equals(JvmAbi.DEFAULT_MODULE_NAME)) {
proto.setExtension(JvmProtoBuf.classModuleName, stringTable.getStringIndex(moduleName));
}
writeLocalProperties(proto, typeMapper.mapClass(descriptor), JvmProtoBuf.classLocalVariable);
}
@Override
@@ -84,6 +95,29 @@ public class JvmSerializerExtension extends SerializerExtension {
}
}
public void serializeJvmPackage(@NotNull ProtoBuf.Package.Builder proto, @NotNull Type partAsmType) {
writeLocalProperties(proto, partAsmType, JvmProtoBuf.packageLocalVariable);
}
private <MessageType extends GeneratedMessageLite.ExtendableMessage<MessageType>,
BuilderType extends GeneratedMessageLite.ExtendableBuilder<MessageType, BuilderType>> void writeLocalProperties(
@NotNull BuilderType proto,
@NotNull Type classAsmType,
@NotNull GeneratedMessageLite.GeneratedExtension<MessageType, List<ProtoBuf.Property>> extension
) {
List<VariableDescriptorWithAccessors> localVariables = codegenBinding.get(CodegenBinding.DELEGATED_PROPERTIES, classAsmType);
if (localVariables == null) return;
for (VariableDescriptorWithAccessors localVariable : localVariables) {
if (localVariable instanceof LocalVariableDescriptor) {
PropertyDescriptor propertyDescriptor =
FakeDescriptorsForReferencesKt.createFreeFakeLocalPropertyDescriptor((LocalVariableDescriptor) localVariable);
DescriptorSerializer serializer = DescriptorSerializer.createForLambda(this);
proto.addExtension(extension, serializer.propertyProto(propertyDescriptor).build());
}
}
}
@Override
public void serializeFlexibleType(
@NotNull FlexibleType flexibleType,
@@ -26,8 +26,8 @@ import org.jetbrains.kotlin.types.TypeSubstitutor;
public class LocalVariableDescriptor extends VariableDescriptorWithInitializerImpl implements VariableDescriptorWithAccessors {
private final boolean isDelegated;
private VariableAccessorDescriptor getter;
private VariableAccessorDescriptor setter;
private LocalVariableAccessorDescriptor.Getter getter;
private LocalVariableAccessorDescriptor.Setter setter;
public LocalVariableDescriptor(
@NotNull DeclarationDescriptor containingDeclaration,
@@ -73,13 +73,13 @@ public class LocalVariableDescriptor extends VariableDescriptorWithInitializerIm
@Nullable
@Override
public VariableAccessorDescriptor getGetter() {
public LocalVariableAccessorDescriptor.Getter getGetter() {
return getter;
}
@Nullable
@Override
public VariableAccessorDescriptor getSetter() {
public LocalVariableAccessorDescriptor.Setter getSetter() {
return setter;
}
@@ -0,0 +1,75 @@
// TARGET_BACKEND: JVM
// WITH_REFLECT
import kotlin.reflect.*
import kotlin.test.*
object Delegate {
lateinit var property: KProperty<*>
operator fun getValue(instance: Any?, kProperty: KProperty<*>): List<String?> {
property = kProperty
return emptyList()
}
operator fun setValue(instance: Any?, kProperty: KProperty<*>, value: List<String?>) {
throw AssertionError()
}
}
fun check(expectedName: String, p: KProperty0<*>): String? {
assertEquals(expectedName, p.name)
assertEquals(emptyList<KParameter>(), p.parameters)
assertEquals(emptyList<KTypeParameter>(), p.typeParameters)
assertEquals(null, p.visibility) // "local" visibility is not representable with reflection API
assertEquals("kotlin.collections.List<kotlin.String?>", p.returnType.toString())
assertTrue(p.isFinal)
assertFalse(p.isOpen)
assertFalse(p.isAbstract)
assertFalse(p.isLateinit)
assertFalse(p.isConst)
// TODO: support getDelegate for local delegated properties
assertEquals(null, (p as KProperty0<*>).getDelegate())
assertEquals(emptyList<KParameter>(), p.getter.parameters)
assertEquals("kotlin.collections.List<kotlin.String?>", p.getter.returnType.toString())
// TODO: support annotations
assertEquals(emptyList<Annotation>(), p.annotations)
try {
p.call()
return "Fail: reflective call of a local delegated property should fail because it's not supported"
} catch (e: UnsupportedOperationException) { /* ok */ }
if (p is KMutableProperty0<*>) {
assertEquals(listOf("kotlin.collections.List<kotlin.String?>"), p.setter.parameters.map { it.type.toString() })
assertEquals("kotlin.Unit", p.setter.returnType.toString())
try {
p.setter.call()
return "Fail: reflective call of a local delegated property setter should fail because it's not supported"
} catch (e: UnsupportedOperationException) { /* ok */ }
}
return null
}
annotation class Anno
fun box(): String {
@Anno
val localVal by Delegate
localVal
check("localVal", Delegate.property as KProperty0<*>)?.let { error -> return error }
@Anno
var localVar by Delegate
localVar
check("localVar", Delegate.property as KProperty0<*>)?.let { error -> return error }
return "OK"
}
@@ -0,0 +1,43 @@
// TARGET_BACKEND: JVM
// WITH_REFLECT
// FILE: 1.kt
@file:JvmMultifileClass
@file:JvmName("Test")
package test
import kotlin.reflect.*
object Delegate {
lateinit var property: KProperty<*>
operator fun getValue(instance: Any?, kProperty: KProperty<*>): List<String?> {
property = kProperty
return emptyList()
}
}
// FILE: 2.kt
@file:JvmMultifileClass
@file:JvmName("Test")
package test
fun foo() {
val x by Delegate
x
}
// FILE: test.kt
import test.*
import kotlin.test.assertEquals
fun box(): String {
foo()
assertEquals("val x: kotlin.collections.List<kotlin.String?>", Delegate.property.toString())
return "OK"
}
@@ -0,0 +1,31 @@
// TARGET_BACKEND: JVM
// WITH_REFLECT
import kotlin.reflect.*
import kotlin.test.*
class Delegate<out T>(val value: T) {
lateinit var property: KProperty<*>
operator fun getValue(instance: Any?, kProperty: KProperty<*>): T {
property = kProperty
return value
}
}
class A<X> {
inner class B<Y> {
fun <Z> foo() {
val delegate = Delegate<Map<Pair<X, Y>, Z>>(emptyMap())
val c: Map<Pair<X, Y>, Z> by delegate
c
assertEquals("kotlin.collections.Map<kotlin.Pair<X, Y>, Z>", delegate.property.returnType.toString())
}
}
}
fun box(): String {
A<String>().B<Int>().foo<Double>()
return "OK"
}
@@ -16041,6 +16041,33 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes
doTest(fileName);
}
}
@TestMetadata("compiler/testData/codegen/box/reflection/properties/localDelegated")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class LocalDelegated extends AbstractIrBlackBoxCodegenTest {
public void testAllFilesPresentInLocalDelegated() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/reflection/properties/localDelegated"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JVM, true);
}
@TestMetadata("localDelegatedProperty.kt")
public void testLocalDelegatedProperty() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/reflection/properties/localDelegated/localDelegatedProperty.kt");
doTest(fileName);
}
@TestMetadata("multiFileClass.kt")
public void testMultiFileClass() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/reflection/properties/localDelegated/multiFileClass.kt");
doTest(fileName);
}
@TestMetadata("variableOfGenericType.kt")
public void testVariableOfGenericType() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/reflection/properties/localDelegated/variableOfGenericType.kt");
doTest(fileName);
}
}
}
@TestMetadata("compiler/testData/codegen/box/reflection/specialBuiltIns")
@@ -16041,6 +16041,33 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
doTest(fileName);
}
}
@TestMetadata("compiler/testData/codegen/box/reflection/properties/localDelegated")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class LocalDelegated extends AbstractBlackBoxCodegenTest {
public void testAllFilesPresentInLocalDelegated() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/reflection/properties/localDelegated"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JVM, true);
}
@TestMetadata("localDelegatedProperty.kt")
public void testLocalDelegatedProperty() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/reflection/properties/localDelegated/localDelegatedProperty.kt");
doTest(fileName);
}
@TestMetadata("multiFileClass.kt")
public void testMultiFileClass() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/reflection/properties/localDelegated/multiFileClass.kt");
doTest(fileName);
}
@TestMetadata("variableOfGenericType.kt")
public void testVariableOfGenericType() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/reflection/properties/localDelegated/variableOfGenericType.kt");
doTest(fileName);
}
}
}
@TestMetadata("compiler/testData/codegen/box/reflection/specialBuiltIns")
@@ -16041,6 +16041,33 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes
doTest(fileName);
}
}
@TestMetadata("compiler/testData/codegen/box/reflection/properties/localDelegated")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class LocalDelegated extends AbstractLightAnalysisModeTest {
public void testAllFilesPresentInLocalDelegated() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/reflection/properties/localDelegated"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JVM, true);
}
@TestMetadata("localDelegatedProperty.kt")
public void testLocalDelegatedProperty() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/reflection/properties/localDelegated/localDelegatedProperty.kt");
doTest(fileName);
}
@TestMetadata("multiFileClass.kt")
public void testMultiFileClass() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/reflection/properties/localDelegated/multiFileClass.kt");
doTest(fileName);
}
@TestMetadata("variableOfGenericType.kt")
public void testVariableOfGenericType() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/reflection/properties/localDelegated/variableOfGenericType.kt");
doTest(fileName);
}
}
}
@TestMetadata("compiler/testData/codegen/box/reflection/specialBuiltIns")