From 493ba62d09fb8a3790701b831adb825e284b48b4 Mon Sep 17 00:00:00 2001 From: Filipp Zhinkin Date: Thu, 14 Dec 2023 17:34:36 +0100 Subject: [PATCH] Add all interface's class-level annotations to its DefaultImpls class. Fixes #155 --- src/main/kotlin/api/AsmMetadataLoading.kt | 4 +++- src/main/kotlin/api/KotlinSignaturesLoading.kt | 15 +++++++++++++-- src/test/kotlin/cases/default/default.txt | 3 +++ src/test/kotlin/cases/default/functions.kt | 17 ++++++++++++++++- src/test/kotlin/tests/CasesPublicAPITest.kt | 2 +- 5 files changed, 36 insertions(+), 5 deletions(-) diff --git a/src/main/kotlin/api/AsmMetadataLoading.kt b/src/main/kotlin/api/AsmMetadataLoading.kt index d2268c18..cc28a632 100644 --- a/src/main/kotlin/api/AsmMetadataLoading.kt +++ b/src/main/kotlin/api/AsmMetadataLoading.kt @@ -51,7 +51,9 @@ const val publishedApiAnnotationName = "kotlin/PublishedApi" fun ClassNode.isPublishedApi() = findAnnotation(publishedApiAnnotationName, includeInvisible = true) != null fun List.isPublishedApi() = firstOrNull { it.refersToName(publishedApiAnnotationName) } != null -fun ClassNode.isDefaultImpls(metadata: KotlinClassMetadata?) = isInner() && name.endsWith("\$DefaultImpls") && metadata.isSyntheticClass() +internal const val DefaultImplsNameSuffix = "\$DefaultImpls" +fun ClassNode.isDefaultImpls(metadata: KotlinClassMetadata?) = + isInner() && name.endsWith(DefaultImplsNameSuffix) && metadata.isSyntheticClass() fun ClassNode.findAnnotation(annotationName: String, includeInvisible: Boolean = false) = diff --git a/src/main/kotlin/api/KotlinSignaturesLoading.kt b/src/main/kotlin/api/KotlinSignaturesLoading.kt index 7606954a..b5495b5c 100644 --- a/src/main/kotlin/api/KotlinSignaturesLoading.kt +++ b/src/main/kotlin/api/KotlinSignaturesLoading.kt @@ -92,11 +92,22 @@ public fun Sequence.loadApiFromJvmClasses(visibilityFilter: (String it.isEffectivelyPublic(classAccess, mVisibility) } + /** + * For synthetic $DefaultImpls classes copy annotations from the original interface + */ + val inheritedAnnotations = mutableListOf().apply { + if (classNode.isDefaultImpls(kotlinMetadata)) { + val originalInterface = classNodeMap[classNode.name.dropLast(DefaultImplsNameSuffix.length)] + addAll(originalInterface?.visibleAnnotations.orEmpty()) + addAll(originalInterface?.invisibleAnnotations.orEmpty()) + } + } + ClassBinarySignature( name, superName, outerClassName, supertypes, fieldSignatures + methodSignatures, classAccess, isEffectivelyPublic(mVisibility), metadata.isFileOrMultipartFacade() || isDefaultImpls(metadata), - annotations(visibleAnnotations, invisibleAnnotations) + annotations(visibleAnnotations, invisibleAnnotations) + inheritedAnnotations ) } } @@ -135,7 +146,7 @@ public fun List.filterOutAnnotated(targetAnnotations: Set< } signature.copy(memberSignatures = notAnnotatedMemberSignatures) - } + }.filterNot { it.isNotUsedWhenEmpty && it.memberSignatures.isEmpty() } } private fun List.filterOutNotAnnotated( diff --git a/src/test/kotlin/cases/default/default.txt b/src/test/kotlin/cases/default/default.txt index 3b40a33f..6e6eb8a9 100644 --- a/src/test/kotlin/cases/default/default.txt +++ b/src/test/kotlin/cases/default/default.txt @@ -15,6 +15,9 @@ public final class cases/default/InterfaceFunctions$DefaultImpls { public static synthetic fun withSomeDefaults$default (Lcases/default/InterfaceFunctions;ILjava/lang/String;ILjava/lang/Object;)V } +public abstract interface class cases/default/PublicInterfaceWithAllFunctionsFilteredOut { +} + public final class cases/default/PublishedApiWithDefaultsKt { public static final fun twoDefaults (ILjava/lang/Object;Ljava/lang/String;)V public static synthetic fun twoDefaults$default (ILjava/lang/Object;Ljava/lang/String;ILjava/lang/Object;)V diff --git a/src/test/kotlin/cases/default/functions.kt b/src/test/kotlin/cases/default/functions.kt index bff3c9dd..5c932926 100644 --- a/src/test/kotlin/cases/default/functions.kt +++ b/src/test/kotlin/cases/default/functions.kt @@ -21,4 +21,19 @@ interface InterfaceFunctions { fun withSomeDefaults(par1: Int, par2: String? = null) -} \ No newline at end of file +} + +@PrivateApi +annotation class PrivateApi + +@PrivateApi +interface PrivateInterfaceWithDefaultMethod { + fun foo() {} + + fun bar() {} +} + +interface PublicInterfaceWithAllFunctionsFilteredOut { + @PrivateApi + fun foo() {} +} diff --git a/src/test/kotlin/tests/CasesPublicAPITest.kt b/src/test/kotlin/tests/CasesPublicAPITest.kt index ae66e059..b0ce7674 100644 --- a/src/test/kotlin/tests/CasesPublicAPITest.kt +++ b/src/test/kotlin/tests/CasesPublicAPITest.kt @@ -29,7 +29,7 @@ class CasesPublicAPITest { @Test fun companions() { snapshotAPIAndCompare(testName.methodName) } - @Test fun default() { snapshotAPIAndCompare(testName.methodName) } + @Test fun default() { snapshotAPIAndCompare(testName.methodName, setOf("cases/default/PrivateApi")) } @Test fun inline() { snapshotAPIAndCompare(testName.methodName) }