Skip to content

Commit

Permalink
Properly dump $default counterparts of @PublishedApi methods (#30)
Browse files Browse the repository at this point in the history
  • Loading branch information
qwwdfsad authored Dec 10, 2020
1 parent 162fd3c commit 38eee9b
Show file tree
Hide file tree
Showing 6 changed files with 55 additions and 16 deletions.
18 changes: 18 additions & 0 deletions src/main/kotlin/api/KotlinMetadataSignature.kt
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,24 @@ data class MethodBinarySignature(
return super.findMemberVisibility(classVisibility) ?: classVisibility?.let { alternateDefaultSignature(it.name)?.let(it::findMember) }
}

/**
* Checks whether the method is a $default counterpart of internal @PublishedApi method
*/
public fun isPublishedApiWithDefaultArguments(
classVisibility: ClassVisibility?,
publishedApiSignatures: Set<JvmMethodSignature>
): Boolean {
// Fast-path
findMemberVisibility(classVisibility)?.isInternal() ?: return false
val name = jvmMember.name
if (!name.endsWith("\$default")) return false
// Leverage the knowledge about modified signature
val expectedPublishedApiCounterPart = JvmMethodSignature(
name.removeSuffix("\$default"),
jvmMember.desc.replace( ";ILjava/lang/Object;)", ";)"))
return expectedPublishedApiCounterPart in publishedApiSignatures
}

private fun isAccessOrAnnotationsMethod() = access.isSynthetic && (name.startsWith("access\$") || name.endsWith("\$annotations"))

private fun isDummyDefaultConstructor() = access.isSynthetic && name == "<init>" && desc == "(Lkotlin/jvm/internal/DefaultConstructorMarker;)V"
Expand Down
4 changes: 2 additions & 2 deletions src/main/kotlin/api/KotlinMetadataVisibilities.kt
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ fun MemberVisibility.isPublic(isPublishedApi: Boolean) =
// Assuming isReified implies inline
!isReified && isPublic(visibility, isPublishedApi)


fun MemberVisibility.isInternal(): Boolean = visibility != null && Flag.IS_INTERNAL(visibility)

val ClassNode.kotlinMetadata: KotlinClassMetadata?
get() {
Expand All @@ -68,7 +68,7 @@ fun KotlinClassMetadata?.isFileOrMultipartFacade() =

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

fun KotlinClassMetadata.toClassVisibility(classNode: ClassNode): ClassVisibility? {
fun KotlinClassMetadata.toClassVisibility(classNode: ClassNode): ClassVisibility {
var flags: Flags? = null
var _facadeClassName: String? = null
val members = mutableListOf<MemberVisibility>()
Expand Down
31 changes: 21 additions & 10 deletions src/main/kotlin/api/KotlinSignaturesLoading.kt
Original file line number Diff line number Diff line change
Expand Up @@ -28,27 +28,38 @@ public fun Sequence<InputStream>.loadApiFromJvmClasses(visibilityFilter: (String

val visibilityMapNew = classNodes.readKotlinVisibilities().filterKeys(visibilityFilter)
return classNodes
.map { classNode -> with(classNode) {
.map { classNode ->
with(classNode) {
val metadata = kotlinMetadata
val mVisibility = visibilityMapNew[name]
val classAccess = AccessFlags(effectiveAccess and Opcodes.ACC_STATIC.inv())

val supertypes = listOf(superName) - "java/lang/Object" + interfaces.sorted()

val memberSignatures = (
fields.map { it.toFieldBinarySignature() } +
methods.map { it.toMethodBinarySignature() }
).filter {
it.isEffectivelyPublic(classAccess, mVisibility)
}
val fieldSignatures = fields
.map { it.toFieldBinarySignature() }
.filter {
it.isEffectivelyPublic(classAccess, mVisibility)
}

val allMethods = methods.map { it.toMethodBinarySignature() }
// Signatures marked with @PublishedApi
val publishedApiSignatures = allMethods.filter {
it.isPublishedApi
}.map { it.jvmMember }.toSet()
val methodSignatures = allMethods
.filter {
it.isEffectivelyPublic(classAccess, mVisibility) ||
it.isPublishedApiWithDefaultArguments(mVisibility, publishedApiSignatures)
}

ClassBinarySignature(
name, superName, outerClassName, supertypes, memberSignatures, classAccess,
name, superName, outerClassName, supertypes, fieldSignatures + methodSignatures, classAccess,
isEffectivelyPublic(mVisibility),
metadata.isFileOrMultipartFacade() || isDefaultImpls(metadata),
annotations(visibleAnnotations, invisibleAnnotations)
)
}}
}
}
.asIterable()
.sortedBy { it.name }
}
Expand Down
5 changes: 5 additions & 0 deletions src/test/kotlin/cases/default/default.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,8 @@ public final class cases/default/InterfaceFunctions$DefaultImpls {
public static synthetic fun withSomeDefaults$default (Lcases/default/InterfaceFunctions;ILjava/lang/String;ILjava/lang/Object;)V
}

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
}

9 changes: 9 additions & 0 deletions src/test/kotlin/cases/default/publishedApiWithDefaults.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/*
* Copyright 2016-2020 JetBrains s.r.o.
* Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file.
*/
@file:Suppress("UNUSED_PARAMETER")
package cases.default

@PublishedApi
internal fun twoDefaults(par0: Int, par1: Any = 1, par2: String? = null) {}
4 changes: 0 additions & 4 deletions src/test/kotlin/tests/CasesPublicAPITest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -50,18 +50,14 @@ class CasesPublicAPITest {

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


private fun snapshotAPIAndCompare(testClassRelativePath: String) {
val testClassPaths = baseClassPaths.map { it.resolve(testClassRelativePath) }
val testClasses = testClassPaths.flatMap { it.listFiles().orEmpty().asIterable() }
check(testClasses.isNotEmpty()) { "No class files are found in paths: $testClassPaths" }

val testClassStreams = testClasses.asSequence().filter { it.name.endsWith(".class") }.map { it.inputStream() }

val api = testClassStreams.loadApiFromJvmClasses().filterOutNonPublic()

val target = baseOutputPath.resolve(testClassRelativePath).resolve(testName.methodName + ".txt")

api.dumpAndCompareWith(target)
}
}

0 comments on commit 38eee9b

Please sign in to comment.