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 generateKotlinAnnotation() {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void generateSyntheticParts() {
|
protected abstract void generateSyntheticParts();
|
||||||
}
|
|
||||||
|
|
||||||
private void generateClassBody() {
|
private void generateClassBody() {
|
||||||
FunctionCodegen functionCodegen = new FunctionCodegen(context, v, state, this);
|
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.asm4.commons.Method;
|
||||||
import org.jetbrains.jet.codegen.context.CodegenContext;
|
import org.jetbrains.jet.codegen.context.CodegenContext;
|
||||||
import org.jetbrains.jet.codegen.context.FieldOwnerContext;
|
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.context.NamespaceFacadeContext;
|
||||||
import org.jetbrains.jet.codegen.signature.JvmMethodSignature;
|
import org.jetbrains.jet.codegen.signature.JvmMethodSignature;
|
||||||
import org.jetbrains.jet.codegen.state.GenerationState;
|
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.AsmUtil.*;
|
||||||
import static org.jetbrains.jet.codegen.CodegenUtil.getParentBodyCodegen;
|
import static org.jetbrains.jet.codegen.CodegenUtil.getParentBodyCodegen;
|
||||||
import static org.jetbrains.jet.codegen.CodegenUtil.isInterface;
|
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;
|
import static org.jetbrains.jet.lang.resolve.java.AsmTypeConstants.OBJECT_TYPE;
|
||||||
|
|
||||||
public class PropertyCodegen extends GenerationStateAware {
|
public class PropertyCodegen extends GenerationStateAware {
|
||||||
@@ -142,26 +142,41 @@ public class PropertyCodegen extends GenerationStateAware {
|
|||||||
AnnotationCodegen.forField(fieldVisitor, typeMapper).genAnnotations(propertyDescriptor);
|
AnnotationCodegen.forField(fieldVisitor, typeMapper).genAnnotations(propertyDescriptor);
|
||||||
}
|
}
|
||||||
else if (!propertyDescriptor.getAnnotations().isEmpty()) {
|
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
|
if (!isTrait(context.getContextDescriptor())) {
|
||||||
// accessible via reflection, and 'deprecated' and 'private' flags prevent this method from being called accidentally
|
Method method = getSyntheticMethodSignature(typeMapper, propertyDescriptor);
|
||||||
ReceiverParameterDescriptor receiver = propertyDescriptor.getReceiverParameter();
|
generateSyntheticMethodForAnnotatedProperty(v, typeMapper, propertyDescriptor, method);
|
||||||
Type receiverAsmType = receiver == null ? null : typeMapper.mapType(receiver.getType());
|
v.getMemberMap().recordSyntheticMethodOfProperty(propertyDescriptor, method);
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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) {
|
private FieldVisitor generateBackingField(JetNamedDeclaration element, PropertyDescriptor propertyDescriptor, boolean isDelegate, JetType jetType, Object defaultValue) {
|
||||||
int modifiers = getDeprecatedAccessFlag(propertyDescriptor);
|
int modifiers = getDeprecatedAccessFlag(propertyDescriptor);
|
||||||
|
|
||||||
|
|||||||
@@ -18,10 +18,16 @@ package org.jetbrains.jet.codegen;
|
|||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
import org.jetbrains.asm4.commons.Method;
|
||||||
import org.jetbrains.jet.codegen.context.ClassContext;
|
import org.jetbrains.jet.codegen.context.ClassContext;
|
||||||
import org.jetbrains.jet.codegen.state.GenerationState;
|
import org.jetbrains.jet.codegen.state.GenerationState;
|
||||||
import org.jetbrains.jet.codegen.state.JetTypeMapperMode;
|
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.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.*;
|
import static org.jetbrains.asm4.Opcodes.*;
|
||||||
|
|
||||||
@@ -49,6 +55,25 @@ public class TraitImplBodyCodegen extends ClassBodyCodegen {
|
|||||||
v.visitSource(myClass.getContainingFile().getName(), null);
|
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
|
@NotNull
|
||||||
private String jvmName() {
|
private String jvmName() {
|
||||||
return typeMapper.mapType(descriptor.getDefaultType(), JetTypeMapperMode.TRAIT_IMPL).getInternalName();
|
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() {
|
public void testInClass() {
|
||||||
loadFile();
|
loadFile();
|
||||||
assertClassHasAnnotatedSyntheticMethod(generateClass("A"));
|
assertAnnotatedSyntheticMethodExistence(true, generateClass("A"));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testTopLevel() {
|
public void testTopLevel() {
|
||||||
@@ -55,14 +55,24 @@ public class SyntheticMethodForAnnotatedPropertyGenTest extends CodegenTestCase
|
|||||||
if (fileName.startsWith(packageClassName) && !fileName.equals(packageClassName + ".class")) {
|
if (fileName.startsWith(packageClassName) && !fileName.equals(packageClassName + ".class")) {
|
||||||
// This should be package$src class
|
// This should be package$src class
|
||||||
Class<?> a = generateClass(fileName.substring(0, fileName.length() - ".class".length()));
|
Class<?> a = generateClass(fileName.substring(0, fileName.length() - ".class".length()));
|
||||||
assertClassHasAnnotatedSyntheticMethod(a);
|
assertAnnotatedSyntheticMethodExistence(true, a);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void assertClassHasAnnotatedSyntheticMethod(@NotNull Class<?> a) {
|
public void testInTrait() throws ClassNotFoundException {
|
||||||
for (Method method : a.getDeclaredMethods()) {
|
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 (TEST_SYNTHETIC_METHOD_NAME.equals(method.getName())) {
|
||||||
|
if (!expected) {
|
||||||
|
fail("Synthetic method for annotated property found, but not expected: " + method);
|
||||||
|
}
|
||||||
assertTrue(method.isSynthetic());
|
assertTrue(method.isSynthetic());
|
||||||
int modifiers = method.getModifiers();
|
int modifiers = method.getModifiers();
|
||||||
assertTrue(Modifier.isFinal(modifiers));
|
assertTrue(Modifier.isFinal(modifiers));
|
||||||
@@ -75,6 +85,8 @@ public class SyntheticMethodForAnnotatedPropertyGenTest extends CodegenTestCase
|
|||||||
return;
|
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