Skip to content

Commit 06cf7c8

Browse files
martinbonninqwwdfsad
authored andcommitted
track annotations on properties
1 parent 75de9a4 commit 06cf7c8

File tree

6 files changed

+97
-19
lines changed

6 files changed

+97
-19
lines changed

src/main/kotlin/api/KotlinMetadataSignature.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,12 +106,13 @@ data class MethodBinarySignature(
106106
}
107107
}
108108

109-
fun MethodNode.toMethodBinarySignature() =
109+
fun MethodNode.toMethodBinarySignature(propertyAnnotations: List<AnnotationNode>?) =
110110
MethodBinarySignature(
111111
JvmMethodSignature(name, desc),
112112
isPublishedApi(),
113113
AccessFlags(access),
114-
annotations(visibleAnnotations, invisibleAnnotations))
114+
visibleAnnotations.orEmpty() + invisibleAnnotations.orEmpty() + propertyAnnotations.orEmpty()
115+
)
115116

116117
data class FieldBinarySignature(
117118
override val jvmMember: JvmFieldSignature,

src/main/kotlin/api/KotlinMetadataVisibilities.kt

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,12 @@ fun ClassVisibility.findMember(signature: JvmMemberSignature): MemberVisibility?
2727
members[signature] ?: partVisibilities.mapNotNull { it.members[signature] }.firstOrNull()
2828

2929

30-
data class MemberVisibility(val member: JvmMemberSignature, val visibility: Flags?, val isReified: Boolean)
30+
data class MemberVisibility(
31+
val member: JvmMemberSignature,
32+
val visibility: Flags?,
33+
val isReified: Boolean,
34+
val annotationHolders: PropertyAnnotationHolders
35+
)
3136

3237
private fun isPublic(visibility: Flags?, isPublishedApi: Boolean) =
3338
visibility == null
@@ -76,14 +81,28 @@ fun KotlinClassMetadata?.isFileOrMultipartFacade() =
7681

7782
fun KotlinClassMetadata?.isSyntheticClass() = this is KotlinClassMetadata.SyntheticClass
7883

84+
class PropertyAnnotationHolders(
85+
val field: JvmMemberSignature?,
86+
val method: JvmMethodSignature?,
87+
) {
88+
companion object {
89+
val None = PropertyAnnotationHolders(null, null)
90+
}
91+
}
92+
7993
fun KotlinClassMetadata.toClassVisibility(classNode: ClassNode): ClassVisibility {
8094
var flags: Flags? = null
8195
var _facadeClassName: String? = null
8296
val members = mutableListOf<MemberVisibility>()
8397

84-
fun addMember(signature: JvmMemberSignature?, flags: Flags, isReified: Boolean) {
98+
fun addMember(
99+
signature: JvmMemberSignature?,
100+
flags: Flags,
101+
isReified: Boolean,
102+
annotationHolders: PropertyAnnotationHolders
103+
) {
85104
if (signature != null) {
86-
members.add(MemberVisibility(signature, flags, isReified))
105+
members.add(MemberVisibility(signature, flags, isReified, annotationHolders))
87106
}
88107
}
89108

@@ -93,7 +112,7 @@ fun KotlinClassMetadata.toClassVisibility(classNode: ClassNode): ClassVisibility
93112
flags = klass.flags
94113

95114
for (constructor in klass.constructors) {
96-
addMember(constructor.signature, constructor.flags, isReified = false)
115+
addMember(constructor.signature, constructor.flags, isReified = false, annotationHolders = PropertyAnnotationHolders.None)
97116
}
98117
}
99118
is KotlinClassMetadata.FileFacade ->
@@ -107,20 +126,22 @@ fun KotlinClassMetadata.toClassVisibility(classNode: ClassNode): ClassVisibility
107126
fun List<KmTypeParameter>.containsReified() = any { Flag.TypeParameter.IS_REIFIED(it.flags) }
108127

109128
for (function in container.functions) {
110-
addMember(function.signature, function.flags, function.typeParameters.containsReified())
129+
addMember(function.signature, function.flags, function.typeParameters.containsReified(), PropertyAnnotationHolders.None)
111130
}
112131

113132
for (property in container.properties) {
114133
val isReified = property.typeParameters.containsReified()
115-
addMember(property.getterSignature, property.getterFlags, isReified)
116-
addMember(property.setterSignature, property.setterFlags, isReified)
134+
val annotationDelegates = PropertyAnnotationHolders(property.fieldSignature, property.syntheticMethodForAnnotations)
135+
136+
addMember(property.getterSignature, property.getterFlags, isReified, annotationDelegates)
137+
addMember(property.setterSignature, property.setterFlags, isReified, annotationDelegates)
117138

118139
val fieldVisibility = when {
119140
Flag.Property.IS_LATEINIT(property.flags) -> property.setterFlags
120141
property.getterSignature == null && property.setterSignature == null -> property.flags // JvmField or const case
121142
else -> flagsOf(Flag.IS_PRIVATE)
122143
}
123-
addMember(property.fieldSignature, fieldVisibility, isReified = false)
144+
addMember(property.fieldSignature, fieldVisibility, isReified = false, annotationHolders = PropertyAnnotationHolders.None)
124145
}
125146
}
126147

src/main/kotlin/api/KotlinSignaturesLoading.kt

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55

66
package kotlinx.validation.api
77

8+
import kotlinx.metadata.jvm.JvmFieldSignature
9+
import kotlinx.metadata.jvm.JvmMethodSignature
810
import kotlinx.metadata.jvm.KotlinClassMetadata
911
import kotlinx.validation.*
1012
import org.objectweb.asm.*
@@ -16,7 +18,7 @@ import java.util.jar.*
1618
@ExternalApi
1719
@Suppress("unused")
1820
public fun JarFile.loadApiFromJvmClasses(visibilityFilter: (String) -> Boolean = { true }): List<ClassBinarySignature> =
19-
classEntries().map { entry -> getInputStream(entry) }.loadApiFromJvmClasses(visibilityFilter)
21+
classEntries().map { entry -> getInputStream(entry) }.loadApiFromJvmClasses(visibilityFilter)
2022

2123
@ExternalApi
2224
public fun Sequence<InputStream>.loadApiFromJvmClasses(visibilityFilter: (String) -> Boolean = { true }): List<ClassBinarySignature> {
@@ -61,7 +63,22 @@ public fun Sequence<InputStream>.loadApiFromJvmClasses(visibilityFilter: (String
6163
companionClass.isEffectivelyPublic(visibility)
6264
}
6365

64-
val allMethods = methods.map { it.toMethodBinarySignature() }
66+
val allMethods = methods.map {
67+
/**
68+
* For getters/setters, pull the annotations from the property
69+
*
70+
* This is either on the field if any or in a '$annotations' synthetic function
71+
*/
72+
val annotationHolders = mVisibility?.members?.get(JvmMethodSignature(it.name, it.desc))?.annotationHolders
73+
74+
val propertyAnnotations = annotationHolders?.method?.let { memberSignature->
75+
methods?.firstOrNull { it.name == memberSignature.name && it.desc == memberSignature.desc }?.visibleAnnotations
76+
}.orEmpty() + annotationHolders?.field?.let { memberSignature->
77+
fields?.firstOrNull { it.name == memberSignature.name && it.desc == memberSignature.desc }?.visibleAnnotations
78+
}.orEmpty()
79+
80+
it.toMethodBinarySignature(propertyAnnotations)
81+
}
6582
// Signatures marked with @PublishedApi
6683
val publishedApiSignatures = allMethods.filter {
6784
it.isPublishedApi
@@ -86,14 +103,20 @@ public fun Sequence<InputStream>.loadApiFromJvmClasses(visibilityFilter: (String
86103
public fun List<ClassBinarySignature>.filterOutAnnotated(targetAnnotations: Set<String>): List<ClassBinarySignature> {
87104
if (targetAnnotations.isEmpty()) return this
88105
return filter {
89-
it.annotations.all { ann -> !targetAnnotations.any { ann.refersToName(it) } }
106+
it.annotations.all { ann -> !targetAnnotations.any { ann.refersToName(it) } }
90107
}.map {
91108
ClassBinarySignature(
92109
it.name,
93110
it.superName,
94111
it.outerName,
95112
it.supertypes,
96-
it.memberSignatures.filter { it.annotations.all { ann -> !targetAnnotations.any { ann.refersToName(it) } } },
113+
it.memberSignatures.filter {
114+
it.annotations.all { ann ->
115+
!targetAnnotations.any {
116+
ann.refersToName(it)
117+
}
118+
}
119+
},
97120
it.access,
98121
it.isEffectivelyPublic,
99122
it.isNotUsedWhenEmpty,
@@ -103,7 +126,10 @@ public fun List<ClassBinarySignature>.filterOutAnnotated(targetAnnotations: Set<
103126
}
104127

105128
@ExternalApi
106-
public fun List<ClassBinarySignature>.filterOutNonPublic(nonPublicPackages: Collection<String> = emptyList(), nonPublicClasses: Collection<String> = emptyList()): List<ClassBinarySignature> {
129+
public fun List<ClassBinarySignature>.filterOutNonPublic(
130+
nonPublicPackages: Collection<String> = emptyList(),
131+
nonPublicClasses: Collection<String> = emptyList()
132+
): List<ClassBinarySignature> {
107133
val pathMapper: (String) -> String = { it.replace('.', '/') + '/' }
108134
val nonPublicPackagePaths = nonPublicPackages.map(pathMapper)
109135
val excludedClasses = nonPublicClasses.map(pathMapper)
@@ -156,8 +182,8 @@ public fun <T : Appendable> List<ClassBinarySignature>.dump(to: T): T {
156182
with(to) {
157183
append(classApi.signature).appendLine(" {")
158184
classApi.memberSignatures
159-
.sortedWith(MEMBER_SORT_ORDER)
160-
.forEach { append("\t").appendLine(it.signature) }
185+
.sortedWith(MEMBER_SORT_ORDER)
186+
.forEach { append("\t").appendLine(it.signature) }
161187
appendLine("}\n")
162188
}
163189
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package cases.marker
2+
3+
@Target(AnnotationTarget.FIELD)
4+
annotation class HiddenField
5+
6+
@Target(AnnotationTarget.PROPERTY)
7+
annotation class HiddenProperty
8+
9+
public class Foo {
10+
// HiddenField will be on the field
11+
@HiddenField
12+
var bar1 = 42
13+
14+
// HiddenField will be on a synthetic `$annotations()` method
15+
@HiddenProperty
16+
var bar2 = 42
17+
}
18+
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
public final class cases/marker/Foo {
2+
public fun <init> ()V
3+
}
4+
5+
public abstract interface annotation class cases/marker/HiddenField : java/lang/annotation/Annotation {
6+
}
7+
8+
public abstract interface annotation class cases/marker/HiddenProperty : java/lang/annotation/Annotation {
9+
}
10+

src/test/kotlin/tests/CasesPublicAPITest.kt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ class CasesPublicAPITest {
4040

4141
@Test fun localClasses() { snapshotAPIAndCompare(testName.methodName) }
4242

43+
@Test fun marker() { snapshotAPIAndCompare(testName.methodName, setOf("cases/marker/HiddenField", "cases/marker/HiddenProperty")) }
44+
4345
@Test fun nestedClasses() { snapshotAPIAndCompare(testName.methodName) }
4446

4547
@Test fun private() { snapshotAPIAndCompare(testName.methodName) }
@@ -52,13 +54,13 @@ class CasesPublicAPITest {
5254

5355
@Test fun whenMappings() { snapshotAPIAndCompare(testName.methodName) }
5456

55-
private fun snapshotAPIAndCompare(testClassRelativePath: String) {
57+
private fun snapshotAPIAndCompare(testClassRelativePath: String, nonPublicMarkers: Set<String> = emptySet()) {
5658
val testClassPaths = baseClassPaths.map { it.resolve(testClassRelativePath) }
5759
val testClasses = testClassPaths.flatMap { it.listFiles().orEmpty().asIterable() }
5860
check(testClasses.isNotEmpty()) { "No class files are found in paths: $testClassPaths" }
5961

6062
val testClassStreams = testClasses.asSequence().filter { it.name.endsWith(".class") }.map { it.inputStream() }
61-
val api = testClassStreams.loadApiFromJvmClasses().filterOutNonPublic()
63+
val api = testClassStreams.loadApiFromJvmClasses().filterOutNonPublic().filterOutAnnotated(nonPublicMarkers)
6264
val target = baseOutputPath.resolve(testClassRelativePath).resolve(testName.methodName + ".txt")
6365
api.dumpAndCompareWith(target)
6466
}

0 commit comments

Comments
 (0)