Do not include $DefaultImpls inner classes in public API when they do not contain any public members (i.e. used only as a container to hold synthetic methods with annotations for properties or typealiases)

This commit is contained in:
Ilya Gorbunov
2016-10-13 09:36:21 +03:00
parent 2c9b2652c1
commit 0b3fb41eeb
6 changed files with 87 additions and 3 deletions
@@ -41,7 +41,7 @@ fun getBinaryAPI(classStreams: Sequence<InputStream>, visibilityMap: Map<String,
it.isEffectivelyPublic(classAccess, classVisibility)
}
ClassBinarySignature(name, superName, outerClassName, supertypes, memberSignatures, classAccess, isEffectivelyPublic(classVisibility), isFileOrMultipartFacade())
ClassBinarySignature(name, superName, outerClassName, supertypes, memberSignatures, classAccess, isEffectivelyPublic(classVisibility), isFileOrMultipartFacade() || isDefaultImpls())
}}
.asIterable()
.sortedBy { it.name }
@@ -74,7 +74,7 @@ fun List<ClassBinarySignature>.filterOutNonPublic(): List<ClassBinarySignature>
return filter { it.isPublicAndAccessible() }
.map { it.flattenNonPublicBases() }
.filterNot { it.isFileOrMultipartFacade && it.memberSignatures.isEmpty()}
.filterNot { it.isNotUsedWhenEmpty && it.memberSignatures.isEmpty()}
}
fun List<ClassBinarySignature>.dump() = dump(to = System.out)
@@ -23,7 +23,7 @@ data class ClassBinarySignature(
val memberSignatures: List<MemberBinarySignature>,
val access: AccessFlags,
val isEffectivelyPublic: Boolean,
val isFileOrMultipartFacade: Boolean) {
val isNotUsedWhenEmpty: Boolean) {
val signature: String
get() = "${access.getModifierString()} class $name" + if (supertypes.isEmpty()) "" else " : ${supertypes.joinToString()}"
@@ -121,6 +121,7 @@ fun ClassNode.isEffectivelyPublic(classVisibility: ClassVisibility?) =
val ClassNode.innerClassNode: InnerClassNode? get() = innerClasses.singleOrNull { it.name == name }
fun ClassNode.isLocal() = innerClassNode?.run { innerName == null && outerName == null} ?: false
fun ClassNode.isInner() = innerClassNode != null
fun ClassNode.isWhenMappings() = isSynthetic(access) && name.endsWith("\$WhenMappings")
val ClassNode.effectiveAccess: Int get() = innerClassNode?.access ?: access
@@ -134,12 +135,14 @@ fun FieldNode.isInlineExposed() = hasAnnotation(inlineExposedAnnotationName, inc
private object KotlinClassKind {
const val FILE = 2
const val SYNTHETIC_CLASS = 3
const val MULTIPART_FACADE = 4
val FILE_OR_MULTIPART_FACADE_KINDS = listOf(FILE, MULTIPART_FACADE)
}
fun ClassNode.isFileOrMultipartFacade() = kotlinClassKind.let { it != null && it in KotlinClassKind.FILE_OR_MULTIPART_FACADE_KINDS }
fun ClassNode.isDefaultImpls() = isInner() && name.endsWith("\$DefaultImpls") && kotlinClassKind == KotlinClassKind.SYNTHETIC_CLASS
val ClassNode.kotlinClassKind: Int?
@@ -0,0 +1,22 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cases.interfaces
public interface EmptyImpls {
@SinceKotlin("1.1")
val property: String
}
@@ -0,0 +1,30 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cases.interfaces
public interface BaseWithImpl {
fun foo() = 42
}
public interface DerivedWithImpl : BaseWithImpl {
override fun foo(): Int {
return super.foo() + 1
}
}
public interface DerivedWithoutImpl : BaseWithImpl
@@ -0,0 +1,27 @@
public abstract interface class cases/interfaces/BaseWithImpl {
public abstract fun foo ()I
}
public final class cases/interfaces/BaseWithImpl$DefaultImpls {
public static fun foo (Lcases/interfaces/BaseWithImpl;)I
}
public abstract interface class cases/interfaces/DerivedWithImpl : cases/interfaces/BaseWithImpl {
public abstract fun foo ()I
}
public final class cases/interfaces/DerivedWithImpl$DefaultImpls {
public static fun foo (Lcases/interfaces/DerivedWithImpl;)I
}
public abstract interface class cases/interfaces/DerivedWithoutImpl : cases/interfaces/BaseWithImpl {
}
public final class cases/interfaces/DerivedWithoutImpl$DefaultImpls {
public static fun foo (Lcases/interfaces/DerivedWithoutImpl;)I
}
public abstract interface class cases/interfaces/EmptyImpls {
public abstract fun getProperty ()Ljava/lang/String;
}
@@ -20,6 +20,8 @@ class CasesPublicAPITest {
@Test fun inline() { snapshotAPIAndCompare(testName.methodName) }
@Test fun interfaces() { snapshotAPIAndCompare(testName.methodName) }
@Test fun internal() { snapshotAPIAndCompare(testName.methodName) }
@Test fun java() { snapshotAPIAndCompare(testName.methodName) }