Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add all interface's class-level annotations to its DefaultImpls class. #161

Merged
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion src/main/kotlin/api/AsmMetadataLoading.kt
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,9 @@ const val publishedApiAnnotationName = "kotlin/PublishedApi"
fun ClassNode.isPublishedApi() = findAnnotation(publishedApiAnnotationName, includeInvisible = true) != null
fun List<AnnotationNode>.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) =
Expand Down
15 changes: 13 additions & 2 deletions src/main/kotlin/api/KotlinSignaturesLoading.kt
Original file line number Diff line number Diff line change
Expand Up @@ -92,11 +92,22 @@ public fun Sequence<InputStream>.loadApiFromJvmClasses(visibilityFilter: (String
it.isEffectivelyPublic(classAccess, mVisibility)
}

/**
* For synthetic $DefaultImpls classes copy annotations from the original interface
*/
val inheritedAnnotations = mutableListOf<AnnotationNode>().apply {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you please check if it is possible to consolidate annotation "inheritance" in a single place?

AFAIR we have a multitude of such logic, some examples are:

  1. PublishedApi and methods with default arguments
  2. Regular annotations (e.g. @PrivateApi) and methods with default arguments
  3. Nested objects/companions and @JvmStatic
  4. Companions and INSTANCE
  5. Fields and accessors

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems like now there are only 3 cases when we're "inheriting" ("propagating" would fit better, I guess) annotations for an entity from some other entities:

  • for fields, we're taking annotations from accessors (and with Add all Companion class' annotations to corresponding Companion field. #162 we'll propagate companion class' annotations to the corresponding field);
  • for methods, we're taking annotations from other property accessors and fields and also from a regular method declaration if the current one is "$default";
  • for classes, if a class is "$DefaultImpls", we're taking annotations from the "original" class.

Currently, the logic is local w.r.t. the entity's processing scope (i.e. we're collecting annotations for a field when processing a field, for a method when processing that method and so on) and I'm not sure if we could do significantly better.
There's always an option to rewrite all the processing logic to have a single object encompassing the whole context (instead of tossing parts of that context across various extension functions) and have (still) separate methods responsible for annotation extraction there, but I think it is out of this PR's scope.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for taking a look!

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
)
}
}
Expand Down Expand Up @@ -135,7 +146,7 @@ public fun List<ClassBinarySignature>.filterOutAnnotated(targetAnnotations: Set<
}

signature.copy(memberSignatures = notAnnotatedMemberSignatures)
}
}.filterNot { it.isNotUsedWhenEmpty && it.memberSignatures.isEmpty() }
}

private fun List<ClassBinarySignature>.filterOutNotAnnotated(
Expand Down
3 changes: 3 additions & 0 deletions src/test/kotlin/cases/default/default.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
17 changes: 16 additions & 1 deletion src/test/kotlin/cases/default/functions.kt
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,19 @@ interface InterfaceFunctions {

fun withSomeDefaults(par1: Int, par2: String? = null)

}
}

@PrivateApi
annotation class PrivateApi

@PrivateApi
interface PrivateInterfaceWithDefaultMethod {
fun foo() {}

fun bar() {}
}

interface PublicInterfaceWithAllFunctionsFilteredOut {
@PrivateApi
fun foo() {}
qwwdfsad marked this conversation as resolved.
Show resolved Hide resolved
}
2 changes: 1 addition & 1 deletion src/test/kotlin/tests/CasesPublicAPITest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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) }

Expand Down