Generate synthetic methods for annotated properties in TImpl classes

Traits themselves can't have these methods, since it'd be a weird public method
in an interface

 #KT-4072 Fixed
This commit is contained in:
Alexander Udalov
2013-10-15 22:25:24 +04:00
parent 5539a29439
commit 08bc67b925
5 changed files with 83 additions and 25 deletions
@@ -79,8 +79,7 @@ public abstract class ClassBodyCodegen extends MemberCodegen {
protected void generateKotlinAnnotation() {
}
protected void generateSyntheticParts() {
}
protected abstract void generateSyntheticParts();
private void generateClassBody() {
FunctionCodegen functionCodegen = new FunctionCodegen(context, v, state, this);
@@ -27,7 +27,6 @@ import org.jetbrains.asm4.commons.InstructionAdapter;
import org.jetbrains.asm4.commons.Method;
import org.jetbrains.jet.codegen.context.CodegenContext;
import org.jetbrains.jet.codegen.context.FieldOwnerContext;
import org.jetbrains.jet.codegen.context.NamespaceContext;
import org.jetbrains.jet.codegen.context.NamespaceFacadeContext;
import org.jetbrains.jet.codegen.signature.JvmMethodSignature;
import org.jetbrains.jet.codegen.state.GenerationState;
@@ -49,6 +48,7 @@ import static org.jetbrains.asm4.Opcodes.*;
import static org.jetbrains.jet.codegen.AsmUtil.*;
import static org.jetbrains.jet.codegen.CodegenUtil.getParentBodyCodegen;
import static org.jetbrains.jet.codegen.CodegenUtil.isInterface;
import static org.jetbrains.jet.lang.resolve.DescriptorUtils.isTrait;
import static org.jetbrains.jet.lang.resolve.java.AsmTypeConstants.OBJECT_TYPE;
public class PropertyCodegen extends GenerationStateAware {
@@ -142,26 +142,41 @@ public class PropertyCodegen extends GenerationStateAware {
AnnotationCodegen.forField(fieldVisitor, typeMapper).genAnnotations(propertyDescriptor);
}
else if (!propertyDescriptor.getAnnotations().isEmpty()) {
// Annotations on properties without backing fields are stored in bytecode on an empty synthetic method. This way they're still
// accessible via reflection, and 'deprecated' and 'private' flags prevent this method from being called accidentally
ReceiverParameterDescriptor receiver = propertyDescriptor.getReceiverParameter();
Type receiverAsmType = receiver == null ? null : typeMapper.mapType(receiver.getType());
Method method = JvmAbi.getSyntheticMethodSignatureForAnnotatedProperty(propertyDescriptor.getName(), receiverAsmType);
MethodVisitor mv = v.newMethod(null,
ACC_DEPRECATED | ACC_FINAL | ACC_PRIVATE | ACC_STATIC | ACC_SYNTHETIC,
method.getName(),
method.getDescriptor(),
null,
null);
v.getMemberMap().recordSyntheticMethodOfProperty(propertyDescriptor, method);
AnnotationCodegen.forMethod(mv, typeMapper).genAnnotations(propertyDescriptor);
mv.visitCode();
mv.visitInsn(Opcodes.RETURN);
mv.visitEnd();
if (!isTrait(context.getContextDescriptor())) {
Method method = getSyntheticMethodSignature(typeMapper, propertyDescriptor);
generateSyntheticMethodForAnnotatedProperty(v, typeMapper, propertyDescriptor, method);
v.getMemberMap().recordSyntheticMethodOfProperty(propertyDescriptor, method);
}
}
}
// Annotations on properties without backing fields are stored in bytecode on an empty synthetic method. This way they're still
// accessible via reflection, and 'deprecated' and 'private' flags prevent this method from being called accidentally
public static void generateSyntheticMethodForAnnotatedProperty(
@NotNull ClassBuilder v,
@NotNull JetTypeMapper typeMapper,
@NotNull PropertyDescriptor propertyDescriptor,
@NotNull Method method
) {
MethodVisitor mv = v.newMethod(null,
ACC_DEPRECATED | ACC_FINAL | ACC_PRIVATE | ACC_STATIC | ACC_SYNTHETIC,
method.getName(),
method.getDescriptor(),
null,
null);
AnnotationCodegen.forMethod(mv, typeMapper).genAnnotations(propertyDescriptor);
mv.visitCode();
mv.visitInsn(Opcodes.RETURN);
mv.visitEnd();
}
@NotNull
public static Method getSyntheticMethodSignature(@NotNull JetTypeMapper typeMapper, @NotNull PropertyDescriptor propertyDescriptor) {
ReceiverParameterDescriptor receiver = propertyDescriptor.getReceiverParameter();
Type receiverAsmType = receiver == null ? null : typeMapper.mapType(receiver.getType());
return JvmAbi.getSyntheticMethodSignatureForAnnotatedProperty(propertyDescriptor.getName(), receiverAsmType);
}
private FieldVisitor generateBackingField(JetNamedDeclaration element, PropertyDescriptor propertyDescriptor, boolean isDelegate, JetType jetType, Object defaultValue) {
int modifiers = getDeprecatedAccessFlag(propertyDescriptor);
@@ -18,10 +18,16 @@ package org.jetbrains.jet.codegen;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.asm4.commons.Method;
import org.jetbrains.jet.codegen.context.ClassContext;
import org.jetbrains.jet.codegen.state.GenerationState;
import org.jetbrains.jet.codegen.state.JetTypeMapperMode;
import org.jetbrains.jet.lang.descriptors.PropertyDescriptor;
import org.jetbrains.jet.lang.descriptors.VariableDescriptor;
import org.jetbrains.jet.lang.psi.JetClassOrObject;
import org.jetbrains.jet.lang.psi.JetDeclaration;
import org.jetbrains.jet.lang.psi.JetProperty;
import org.jetbrains.jet.lang.resolve.BindingContext;
import static org.jetbrains.asm4.Opcodes.*;
@@ -49,6 +55,25 @@ public class TraitImplBodyCodegen extends ClassBodyCodegen {
v.visitSource(myClass.getContainingFile().getName(), null);
}
@Override
protected void generateSyntheticParts() {
generateSyntheticMethodsForAnnotatedProperties();
}
private void generateSyntheticMethodsForAnnotatedProperties() {
for (JetDeclaration declaration : myClass.getDeclarations()) {
if (declaration instanceof JetProperty) {
VariableDescriptor variable = bindingContext.get(BindingContext.VARIABLE, declaration);
assert variable instanceof PropertyDescriptor : "Variable in trait should be a property: " + variable;
PropertyDescriptor property = (PropertyDescriptor) variable;
if (!property.getAnnotations().isEmpty()) {
Method method = PropertyCodegen.getSyntheticMethodSignature(typeMapper, property);
PropertyCodegen.generateSyntheticMethodForAnnotatedProperty(v, typeMapper, property, method);
}
}
}
}
@NotNull
private String jvmName() {
return typeMapper.mapType(descriptor.getDefaultType(), JetTypeMapperMode.TRAIT_IMPL).getInternalName();
@@ -0,0 +1,7 @@
import java.lang.annotation.*
Retention(RetentionPolicy.RUNTIME) annotation class SomeAnnotation(val value: String)
trait T {
[SomeAnnotation("OK")] val property: Int
}
@@ -45,7 +45,7 @@ public class SyntheticMethodForAnnotatedPropertyGenTest extends CodegenTestCase
public void testInClass() {
loadFile();
assertClassHasAnnotatedSyntheticMethod(generateClass("A"));
assertAnnotatedSyntheticMethodExistence(true, generateClass("A"));
}
public void testTopLevel() {
@@ -55,14 +55,24 @@ public class SyntheticMethodForAnnotatedPropertyGenTest extends CodegenTestCase
if (fileName.startsWith(packageClassName) && !fileName.equals(packageClassName + ".class")) {
// This should be package$src class
Class<?> a = generateClass(fileName.substring(0, fileName.length() - ".class".length()));
assertClassHasAnnotatedSyntheticMethod(a);
assertAnnotatedSyntheticMethodExistence(true, a);
}
}
}
private static void assertClassHasAnnotatedSyntheticMethod(@NotNull Class<?> a) {
for (Method method : a.getDeclaredMethods()) {
public void testInTrait() throws ClassNotFoundException {
loadFile();
GeneratedClassLoader loader = generateAndCreateClassLoader();
assertAnnotatedSyntheticMethodExistence(false, loader.loadClass("T"));
assertAnnotatedSyntheticMethodExistence(true, loader.loadClass("T" + JvmAbi.TRAIT_IMPL_SUFFIX));
}
private static void assertAnnotatedSyntheticMethodExistence(boolean expected, @NotNull Class<?> clazz) {
for (Method method : clazz.getDeclaredMethods()) {
if (TEST_SYNTHETIC_METHOD_NAME.equals(method.getName())) {
if (!expected) {
fail("Synthetic method for annotated property found, but not expected: " + method);
}
assertTrue(method.isSynthetic());
int modifiers = method.getModifiers();
assertTrue(Modifier.isFinal(modifiers));
@@ -75,6 +85,8 @@ public class SyntheticMethodForAnnotatedPropertyGenTest extends CodegenTestCase
return;
}
}
fail("Synthetic method for annotated property not found: " + TEST_SYNTHETIC_METHOD_NAME);
if (expected) {
fail("Synthetic method for annotated property expected, but not found: " + TEST_SYNTHETIC_METHOD_NAME);
}
}
}