diff --git a/compiler/backend/src/org/jetbrains/jet/codegen/PropertyCodegen.java b/compiler/backend/src/org/jetbrains/jet/codegen/PropertyCodegen.java index ecaa0b50ad0..1815cb705b6 100644 --- a/compiler/backend/src/org/jetbrains/jet/codegen/PropertyCodegen.java +++ b/compiler/backend/src/org/jetbrains/jet/codegen/PropertyCodegen.java @@ -21,6 +21,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.asm4.FieldVisitor; import org.jetbrains.asm4.MethodVisitor; +import org.jetbrains.asm4.Opcodes; import org.jetbrains.asm4.Type; import org.jetbrains.asm4.commons.InstructionAdapter; import org.jetbrains.jet.codegen.context.CodegenContext; @@ -117,8 +118,7 @@ public class PropertyCodegen extends GenerationStateAware { } private void generateBackingField(JetNamedDeclaration p, PropertyDescriptor propertyDescriptor) { - //noinspection ConstantConditions - boolean hasBackingField = bindingContext.get(BindingContext.BACKING_FIELD_REQUIRED, propertyDescriptor); + boolean hasBackingField = Boolean.TRUE.equals(bindingContext.get(BindingContext.BACKING_FIELD_REQUIRED, propertyDescriptor)); boolean isDelegated = p instanceof JetProperty && ((JetProperty) p).getDelegateExpression() != null; if (hasBackingField || isDelegated) { DeclarationDescriptor containingDeclaration = propertyDescriptor.getContainingDeclaration(); @@ -132,7 +132,20 @@ 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 + MethodVisitor mv = v.newMethod(null, + ACC_DEPRECATED | ACC_FINAL | ACC_PRIVATE | ACC_STATIC | ACC_SYNTHETIC, + JvmAbi.getSyntheticMethodNameForAnnotatedProperty(propertyDescriptor.getName()), + JvmAbi.ANNOTATED_PROPERTY_METHOD_SIGNATURE, + null, + null); + AnnotationCodegen.forMethod(mv, typeMapper).genAnnotations(propertyDescriptor); + mv.visitCode(); + mv.visitInsn(Opcodes.RETURN); + mv.visitEnd(); + } } private FieldVisitor generateBackingField(JetNamedDeclaration element, PropertyDescriptor propertyDescriptor, boolean isDelegate, JetType jetType, Object defaultValue) { diff --git a/compiler/frontend.java/src/org/jetbrains/jet/lang/resolve/java/JvmAbi.java b/compiler/frontend.java/src/org/jetbrains/jet/lang/resolve/java/JvmAbi.java index 9fb2d4b0d62..a0951af01cb 100644 --- a/compiler/frontend.java/src/org/jetbrains/jet/lang/resolve/java/JvmAbi.java +++ b/compiler/frontend.java/src/org/jetbrains/jet/lang/resolve/java/JvmAbi.java @@ -17,7 +17,6 @@ package org.jetbrains.jet.lang.resolve.java; import org.jetbrains.annotations.NotNull; -import org.jetbrains.jet.lang.descriptors.PropertyDescriptor; import org.jetbrains.jet.lang.resolve.name.FqName; import org.jetbrains.jet.lang.resolve.name.Name; @@ -38,11 +37,12 @@ public class JvmAbi { public static final String CLASS_OBJECT_CLASS_NAME = "object"; public static final String CLASS_OBJECT_SUFFIX = "$" + CLASS_OBJECT_CLASS_NAME; - public static final String DELEGATED_PROPERTY_NAME_POSTFIX = "$delegate"; + public static final String DELEGATED_PROPERTY_NAME_SUFFIX = "$delegate"; + public static final String ANNOTATED_PROPERTY_METHOD_NAME_SUFFIX = "$annotations"; + public static final String ANNOTATED_PROPERTY_METHOD_SIGNATURE = "()V"; public static final String INSTANCE_FIELD = "instance$"; public static final String CLASS_OBJECT_FIELD = "object$"; - public static final String RECEIVER_PARAMETER = "$receiver"; public static final JvmClassName JETBRAINS_NOT_NULL_ANNOTATION = JvmClassName.byFqNameWithoutInnerClasses("org.jetbrains.annotations.NotNull"); @@ -56,12 +56,18 @@ public class JvmAbi { return fqName.lastSegmentIs(Name.identifier(CLASS_OBJECT_CLASS_NAME)); } + @NotNull public static String getPropertyDelegateName(@NotNull Name name) { - return name.asString() + DELEGATED_PROPERTY_NAME_POSTFIX; + return name.asString() + DELEGATED_PROPERTY_NAME_SUFFIX; } + @NotNull + public static String getSyntheticMethodNameForAnnotatedProperty(@NotNull Name propertyName) { + return propertyName.asString() + ANNOTATED_PROPERTY_METHOD_NAME_SUFFIX; + } - public static String getDefaultPropertyName(Name propertyName, boolean isDelegated, boolean isExtensionProperty) { + @NotNull + public static String getDefaultPropertyName(@NotNull Name propertyName, boolean isDelegated, boolean isExtensionProperty) { if (isDelegated) { return getPropertyDelegateName(propertyName); } diff --git a/compiler/testData/codegen/properties/annotatedClassPropertyNoField.kt b/compiler/testData/codegen/properties/annotatedClassPropertyNoField.kt new file mode 100644 index 00000000000..0abac3c3ad1 --- /dev/null +++ b/compiler/testData/codegen/properties/annotatedClassPropertyNoField.kt @@ -0,0 +1,8 @@ +import java.lang.annotation.* + +Retention(RetentionPolicy.RUNTIME) annotation class SomeAnnotation(val value: String) + +class A { + [SomeAnnotation("OK")] val property: Int + get() = 42 +} diff --git a/compiler/testData/codegen/properties/annotatedPackagePropertyNoField.kt b/compiler/testData/codegen/properties/annotatedPackagePropertyNoField.kt new file mode 100644 index 00000000000..2041c4d1ac4 --- /dev/null +++ b/compiler/testData/codegen/properties/annotatedPackagePropertyNoField.kt @@ -0,0 +1,6 @@ +import java.lang.annotation.* + +Retention(RetentionPolicy.RUNTIME) annotation class SomeAnnotation(val value: String) + +[SomeAnnotation("OK")] val property: Int + get() = 42 diff --git a/compiler/tests/org/jetbrains/jet/codegen/PropertyGenTest.java b/compiler/tests/org/jetbrains/jet/codegen/PropertyGenTest.java index bc4b5bc329c..c4ac72b8e50 100644 --- a/compiler/tests/org/jetbrains/jet/codegen/PropertyGenTest.java +++ b/compiler/tests/org/jetbrains/jet/codegen/PropertyGenTest.java @@ -19,7 +19,12 @@ package org.jetbrains.jet.codegen; import org.jetbrains.annotations.NotNull; import org.jetbrains.asm4.Opcodes; import org.jetbrains.jet.ConfigurationKind; +import org.jetbrains.jet.lang.resolve.java.JvmAbi; +import org.jetbrains.jet.lang.resolve.java.PackageClassUtils; +import org.jetbrains.jet.lang.resolve.name.FqName; +import org.jetbrains.jet.lang.resolve.name.Name; +import java.lang.annotation.Annotation; import java.lang.reflect.*; import static org.jetbrains.jet.codegen.CodegenTestUtil.assertIsCurrentTime; @@ -259,4 +264,41 @@ public class PropertyGenTest extends CodegenTestCase { throw new RuntimeException(e); } } + + private static final String TEST_SYNTHETIC_METHOD_NAME = JvmAbi.getSyntheticMethodNameForAnnotatedProperty(Name.identifier("property")); + + public void testAnnotatedClassPropertyNoField() { + loadFile("properties/annotatedClassPropertyNoField.kt"); + assertClassHasAnnotatedSyntheticMethod(generateClass("A")); + } + + public void testAnnotatedPackagePropertyNoField() { + loadFile("properties/annotatedPackagePropertyNoField.kt"); + String packageClassName = PackageClassUtils.getPackageClassName(FqName.ROOT); + for (String fileName : generateClassesInFile().files()) { + 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); + } + } + } + + private static void assertClassHasAnnotatedSyntheticMethod(@NotNull Class a) { + for (Method method : a.getDeclaredMethods()) { + if (TEST_SYNTHETIC_METHOD_NAME.equals(method.getName())) { + assertTrue(method.isSynthetic()); + int modifiers = method.getModifiers(); + assertTrue(Modifier.isFinal(modifiers)); + assertTrue(Modifier.isStatic(modifiers)); + assertTrue(Modifier.isPrivate(modifiers)); + + Annotation[] annotations = method.getDeclaredAnnotations(); + assertSize(1, annotations); + assertEquals("@SomeAnnotation(value=OK)", annotations[0].toString()); + return; + } + } + fail("Synthetic method for annotated property not found: " + TEST_SYNTHETIC_METHOD_NAME); + } }