diff --git a/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/checkers/JvmFieldApplicabilityChecker.kt b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/checkers/JvmFieldApplicabilityChecker.kt index caa749c8a7a..f0e5c3507df 100644 --- a/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/checkers/JvmFieldApplicabilityChecker.kt +++ b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/checkers/JvmFieldApplicabilityChecker.kt @@ -32,12 +32,10 @@ import org.jetbrains.kotlin.resolve.jvm.diagnostics.ErrorsJvm class JvmFieldApplicabilityChecker : DeclarationChecker { - enum class Problem(val errorMessage: String) { - NOT_A_PROPERTY("JvmField can only be applied to a property"), + internal enum class Problem(val errorMessage: String) { NOT_FINAL("JvmField can only be applied to final property"), PRIVATE("JvmField has no effect on a private property"), CUSTOM_ACCESSOR("JvmField cannot be applied to a property with a custom accessor"), - NO_BACKING_FIELD("JvmField can only be applied to a property with backing field"), OVERRIDES("JvmField cannot be applied to a property that overrides some other property"), LATEINIT("JvmField cannot be applied to lateinit property"), CONST("JvmField cannot be applied to const property"), @@ -54,10 +52,11 @@ class JvmFieldApplicabilityChecker : DeclarationChecker { val annotation = descriptor.findJvmFieldAnnotation() ?: return val problem = when { - descriptor !is PropertyDescriptor -> NOT_A_PROPERTY + // First two cases just prevent duplication of WRONG_ANNOTATION_TARGET + descriptor !is PropertyDescriptor -> return + !descriptor.hasBackingField(bindingContext) -> return descriptor.isOverridable -> NOT_FINAL Visibilities.isPrivate(descriptor.visibility) -> PRIVATE - !descriptor.hasBackingField(bindingContext) -> NO_BACKING_FIELD descriptor.hasCustomAccessor() -> CUSTOM_ACCESSOR descriptor.overriddenDescriptors.isNotEmpty() -> OVERRIDES descriptor.isLateInit -> LATEINIT diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/AnnotationChecker.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/AnnotationChecker.kt index e63860cdc4e..189a3f1b495 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/AnnotationChecker.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/AnnotationChecker.kt @@ -19,6 +19,7 @@ package org.jetbrains.kotlin.resolve import org.jetbrains.kotlin.builtins.KotlinBuiltIns import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.descriptors.DeclarationDescriptor +import org.jetbrains.kotlin.descriptors.PropertyDescriptor import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor import org.jetbrains.kotlin.descriptors.annotations.AnnotationUseSiteTarget import org.jetbrains.kotlin.descriptors.annotations.KotlinRetention @@ -39,7 +40,7 @@ import org.jetbrains.kotlin.types.expressions.ExpressionTypingUtils public class AnnotationChecker(private val additionalCheckers: Iterable) { public fun check(annotated: KtAnnotated, trace: BindingTrace, descriptor: DeclarationDescriptor? = null) { - val actualTargets = getActualTargetList(annotated, descriptor) + val actualTargets = getActualTargetList(annotated, descriptor, trace) checkEntries(annotated.annotationEntries, actualTargets, trace) if (annotated is KtCallableDeclaration) { annotated.typeReference?.let { check(it, trace) } @@ -66,7 +67,7 @@ public class AnnotationChecker(private val additionalCheckers: Iterable { - return getActualTargetList(annotated, descriptor).defaultTargets + public fun getDeclarationSiteActualTargetList(annotated: KtElement, descriptor: ClassDescriptor?, trace: BindingTrace): + List { + return getActualTargetList(annotated, descriptor, trace).defaultTargets } - private fun getActualTargetList(annotated: KtElement, descriptor: DeclarationDescriptor?): TargetList { + private fun DeclarationDescriptor?.hasBackingField(bindingTrace: BindingTrace) + = (this as? PropertyDescriptor)?.let { bindingTrace.get(BindingContext.BACKING_FIELD_REQUIRED, it) } ?: false + + private fun getActualTargetList(annotated: KtElement, descriptor: DeclarationDescriptor?, trace: BindingTrace): TargetList { return when (annotated) { is KtClassOrObject -> (descriptor as? ClassDescriptor)?.let { TargetList(KotlinTarget.classActualTargets(it)) } ?: TargetLists.T_CLASSIFIER @@ -175,9 +180,9 @@ public class AnnotationChecker(private val additionalCheckers: Iterable { if (annotated.hasValOrVar()) @@ -220,13 +225,17 @@ public class AnnotationChecker(private val additionalCheckers: Iterable checkFieldTargetApplicability(annotated, annotation, descriptor) + AnnotationUseSiteTarget.FIELD -> checkIfProperty(annotated, annotation) AnnotationUseSiteTarget.PROPERTY -> checkIfProperty(annotated, annotation) AnnotationUseSiteTarget.PROPERTY_GETTER -> checkIfProperty(annotated, annotation) AnnotationUseSiteTarget.PROPERTY_SETTER -> checkIfMutableProperty(annotated, annotation) @@ -87,20 +87,6 @@ public object AnnotationUseSiteTargetChecker { } } - private fun BindingTrace.checkFieldTargetApplicability( - annotated: KtAnnotated, - annotation: KtAnnotationEntry, - descriptor: DeclarationDescriptor - ) { - if (!checkIfProperty(annotated, annotation)) return - - if (annotated is KtProperty && descriptor is PropertyDescriptor) { - if (!annotated.hasDelegate() && !(bindingContext.get(BindingContext.BACKING_FIELD_REQUIRED, descriptor) ?: false)) { - report(INAPPLICABLE_FIELD_TARGET_NO_BACKING_FIELD.on(annotation)) - } - } - } - private fun BindingTrace.checkIfMutableProperty(annotated: KtAnnotated, annotation: KtAnnotationEntry) { if (!checkIfProperty(annotated, annotation)) return diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/ModifiersChecker.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/ModifiersChecker.kt index 2b9f062145f..cccfa6fa745 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/ModifiersChecker.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/ModifiersChecker.kt @@ -295,7 +295,7 @@ public object ModifierCheckerCore { } } } - val actualTargets = AnnotationChecker.getDeclarationSiteActualTargetList(listOwner, descriptor as? ClassDescriptor) + val actualTargets = AnnotationChecker.getDeclarationSiteActualTargetList(listOwner, descriptor as? ClassDescriptor, trace) val list = listOwner.modifierList ?: return checkModifierList(list, trace, descriptor?.containingDeclaration, actualTargets) } diff --git a/compiler/testData/codegen/bytecodeListing/annotations/defaultTargets.kt b/compiler/testData/codegen/bytecodeListing/annotations/defaultTargets.kt index 6f464df4557..78b3966a80d 100644 --- a/compiler/testData/codegen/bytecodeListing/annotations/defaultTargets.kt +++ b/compiler/testData/codegen/bytecodeListing/annotations/defaultTargets.kt @@ -29,10 +29,6 @@ public class A( @Target(AnnotationTarget.FIELD) annotation class Anno -@Anno -val p: Int - get() = 5 - @Anno val p2: Int = 4 get() = field \ No newline at end of file diff --git a/compiler/testData/codegen/bytecodeListing/annotations/defaultTargets.txt b/compiler/testData/codegen/bytecodeListing/annotations/defaultTargets.txt index fdf5b634822..747f7992733 100644 --- a/compiler/testData/codegen/bytecodeListing/annotations/defaultTargets.txt +++ b/compiler/testData/codegen/bytecodeListing/annotations/defaultTargets.txt @@ -55,6 +55,5 @@ public abstract class Anno public final class DefaultTargetsKt { private final static @Anno field p2: int static method (): void - public final static method getP(): int public final static method getP2(): int } \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/annotations/options/targets/field.kt b/compiler/testData/diagnostics/tests/annotations/options/targets/field.kt new file mode 100644 index 00000000000..a49d7d7a8f3 --- /dev/null +++ b/compiler/testData/diagnostics/tests/annotations/options/targets/field.kt @@ -0,0 +1,44 @@ +@Target(AnnotationTarget.FIELD) +annotation class Field + +@Field +annotation class Another + +@Field +val x: Int = 42 + +@Field +val y: Int + get() = 13 + +@Field +abstract class My(@Field arg: Int, @Field val w: Int) { + @Field + val x: Int = arg + + @Field + val y: Int + get() = 0 + + @Field + abstract val z: Int + + @Field + fun foo() {} + + @Field + val v: Int by Delegates.lazy { 42 } +} + +enum class Your { + @Field FIRST +} + +interface His { + @Field + val x: Int + + @Field + val y: Int + get() = 42 +} diff --git a/compiler/testData/diagnostics/tests/annotations/options/targets/field.txt b/compiler/testData/diagnostics/tests/annotations/options/targets/field.txt new file mode 100644 index 00000000000..9e6847169fd --- /dev/null +++ b/compiler/testData/diagnostics/tests/annotations/options/targets/field.txt @@ -0,0 +1,57 @@ +package + +@Field() public val x: kotlin.Int = 42 +@Field() public val y: kotlin.Int + +@Field() public final annotation class Another : kotlin.Annotation { + public constructor Another() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +@kotlin.annotation.Target(allowedTargets = {AnnotationTarget.FIELD}) public final annotation class Field : kotlin.Annotation { + public constructor Field() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public interface His { + @Field() public abstract val x: kotlin.Int + @Field() public open val y: kotlin.Int + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +@Field() public abstract class My { + public constructor My(/*0*/ @Field() arg: kotlin.Int, /*1*/ w: kotlin.Int) + @Field() public final val v: kotlin.Int + @Field() public final val w: kotlin.Int + @Field() public final val x: kotlin.Int + @Field() public final val y: kotlin.Int + @Field() public abstract val z: kotlin.Int + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + @Field() public final fun foo(): kotlin.Unit + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public final enum class Your : kotlin.Enum { + @Field() enum entry FIRST + + private constructor Your() + public final override /*1*/ /*fake_override*/ val name: kotlin.String + public final override /*1*/ /*fake_override*/ val ordinal: kotlin.Int + protected final override /*1*/ /*fake_override*/ fun clone(): kotlin.Any + public final override /*1*/ /*fake_override*/ fun compareTo(/*0*/ other: Your): kotlin.Int + public final override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public final override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + + // Static members + @kotlin.Deprecated(level = DeprecationLevel.ERROR, message = "Use 'values()' function instead", replaceWith = kotlin.ReplaceWith(expression = "this.values()", imports = {})) public final /*synthesized*/ val values: kotlin.Array + public final /*synthesized*/ fun valueOf(/*0*/ value: kotlin.String): Your + public final /*synthesized*/ fun values(): kotlin.Array +} diff --git a/compiler/testData/diagnostics/tests/annotations/withUseSiteTarget/FieldAnnotations.kt b/compiler/testData/diagnostics/tests/annotations/withUseSiteTarget/FieldAnnotations.kt index b7603798135..33cfb759000 100644 --- a/compiler/testData/diagnostics/tests/annotations/withUseSiteTarget/FieldAnnotations.kt +++ b/compiler/testData/diagnostics/tests/annotations/withUseSiteTarget/FieldAnnotations.kt @@ -18,10 +18,10 @@ class SomeClass { @field:[Ann] protected val simplePropertyWithAnnotationList: String = "text" - @field:Ann + @field:Ann protected val delegatedProperty: String by CustomDelegate() - @field:Ann + @field:Ann val propertyWithCustomGetter: Int get() = 5 diff --git a/compiler/testData/diagnostics/testsWithStdLib/annotations/jvmField/jvmFieldApplicability.kt b/compiler/testData/diagnostics/testsWithStdLib/annotations/jvmField/jvmFieldApplicability.kt index a6eb296757e..edf7042a852 100644 --- a/compiler/testData/diagnostics/testsWithStdLib/annotations/jvmField/jvmFieldApplicability.kt +++ b/compiler/testData/diagnostics/testsWithStdLib/annotations/jvmField/jvmFieldApplicability.kt @@ -1,23 +1,23 @@ // !DIAGNOSTICS: -UNUSED_PARAMETER -UNUSED_VARIABLE -@kotlin.jvm.JvmField +@kotlin.jvm.JvmField fun foo() { - @kotlin.jvm.JvmField val x = "A" + @kotlin.jvm.JvmField val x = "A" } -@JvmField +@JvmField abstract class C : I{ - @kotlin.jvm.JvmField constructor(s: String) { + @kotlin.jvm.JvmField constructor(s: String) { } - @kotlin.jvm.JvmField private fun foo(s: String = "OK") { + @kotlin.jvm.JvmField private fun foo(s: String = "OK") { } - @JvmField val a: String by lazy { "A" } + @JvmField val a: String by lazy { "A" } @JvmField open val b: Int = 3 - @JvmField abstract val c: Int + @JvmField abstract val c: Int @JvmField val customGetter: String = "" @@ -29,7 +29,7 @@ abstract class C : I{ field = s } - @JvmField + @JvmField val noBackingField: String get() = "a" @@ -41,8 +41,8 @@ abstract class C : I{ } interface I { - @JvmField val ai: Int - @JvmField val bi: Int + @JvmField val ai: Int + @JvmField val bi: Int get() = 5 } diff --git a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java index 4c75901db86..d5a3969af6f 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java @@ -1300,6 +1300,12 @@ public class DiagnosticsTestGenerated extends AbstractDiagnosticsTest { doTest(fileName); } + @TestMetadata("field.kt") + public void testField() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/annotations/options/targets/field.kt"); + doTest(fileName); + } + @TestMetadata("file.kt") public void testFile() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/annotations/options/targets/file.kt"); diff --git a/core/descriptors/src/org/jetbrains/kotlin/descriptors/annotations/KotlinTarget.kt b/core/descriptors/src/org/jetbrains/kotlin/descriptors/annotations/KotlinTarget.kt index 9f5d7897949..e9426954a40 100644 --- a/core/descriptors/src/org/jetbrains/kotlin/descriptors/annotations/KotlinTarget.kt +++ b/core/descriptors/src/org/jetbrains/kotlin/descriptors/annotations/KotlinTarget.kt @@ -27,8 +27,8 @@ public enum class KotlinTarget(val description: String, val isDefault: Boolean = CLASS("class"), // includes CLASS_ONLY, OBJECT, COMPANION_OBJECT, OBJECT_LITERAL, INTERFACE, *_CLASS but not ENUM_ENTRY ANNOTATION_CLASS("annotation class"), TYPE_PARAMETER("type parameter", false), - PROPERTY("property"), // includes *_PROPERTY, PROPERTY_PARAMETER, ENUM_ENTRY - FIELD("field"), + PROPERTY("property"), // includes *_PROPERTY (with and without backing field), PROPERTY_PARAMETER, ENUM_ENTRY + FIELD("field"), // includes MEMBER_PROPERTY_WITH_FIELD, TOP_LEVEL_PROPERTY_WITH_FIELD, PROPERTY_PARAMETER, ENUM_ENTRY LOCAL_VARIABLE("local variable"), VALUE_PARAMETER("value parameter"), CONSTRUCTOR("constructor"), @@ -57,8 +57,12 @@ public enum class KotlinTarget(val description: String, val isDefault: Boolean = MEMBER_FUNCTION("member function", false), TOP_LEVEL_FUNCTION("top level function", false), - MEMBER_PROPERTY("member property", false), // includes PROPERTY_PARAMETER - TOP_LEVEL_PROPERTY("top level property", false), + MEMBER_PROPERTY("member property", false), // includes PROPERTY_PARAMETER, with and without field + MEMBER_PROPERTY_WITH_FIELD("member property with backing field", false), + MEMBER_PROPERTY_WITHOUT_FIELD("member property without backing field", false), + TOP_LEVEL_PROPERTY("top level property", false), // with and without field + TOP_LEVEL_PROPERTY_WITH_FIELD("top level property with backing field", false), + TOP_LEVEL_PROPERTY_WITHOUT_FIELD("top level property without backing field", false), INITIALIZER("initializer", false), DESTRUCTURING_DECLARATION("destructuring declaration", false), diff --git a/j2k/testData/fileOrElement/field/volatileTransientAndStrictFp.kt b/j2k/testData/fileOrElement/field/volatileTransientAndStrictFp.kt index 70dea081ac1..b614fdd4a04 100644 --- a/j2k/testData/fileOrElement/field/volatileTransientAndStrictFp.kt +++ b/j2k/testData/fileOrElement/field/volatileTransientAndStrictFp.kt @@ -1,4 +1,4 @@ -// ERROR: This annotation is not applicable to target 'member property' +// ERROR: This annotation is not applicable to target 'member property with backing field' internal class A { @Deprecated("") @Volatile var field1 = 0