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:
@@ -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
|
||||
}
|
||||
+17
-5
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user