Plugin version
3.18.0
Gradle version
9.6.0
JDK version
26
Kotlin and Kotlin Gradle Plugin (KGP) version
2.4.10
Describe the bug
When an internal top-level function has a default argument whose type is an inline value class wrapping a nullable reference, the function's synthetic $default bridge is treated as public API. That keeps the file facade in the ABI, so publicTypeUsage reports a FooKt class even though every top-level member in the file is internal or private. There's no Kotlin syntax to restrict a facade, so the finding is unactionable.
To Reproduce
lib:
@JvmInline
value class Path(val id: String)
@JvmInline
value class PathParent(private val path: Path?) { // nullable → stays boxed
companion object { val Empty = PathParent(null) }
}
consumer (depends on lib), where every top-level member is internal:
internal fun sat(p: PathParent = PathParent.Empty): Boolean = p != PathParent.Empty
internal class User { fun go() = sat() }
Expected behavior
consumer/build/reports/dependency-analysis/main/abi-dump.txt is empty.
Actual behavior
@Lkotlin/Metadata;
public final class com/example/UseKt {
public static synthetic fun sat-WUG17-E$default (Lcom/example/PathParent;ILjava/lang/Object;)Z
}
The function itself is correctly excluded; only the bridge leaks.
Additional context
MethodBinarySignature.alternateDefaultSignature reconstructs the non-bridge signature to look up the real function's visibility in Kotlin metadata, but the bridge's descriptor carries the boxed wrapper type while metadata records the unboxed underlying type:
reconstructed: sat-WUG17-E (Lcom/example/PathParent;)Z
in kotlin.Metadata: sat-WUG17-E (Ljava/lang/String;)Z
A value class over a non-nullable reference erases to its underlying type so this works. Over a nullable reference it must stay boxed, so they diverge, the lookup misses, and isEffectivelyPublic's ?: true fallback treats the bridge as public.
Note the facade handling itself is working. isFileOrMultipartFacade() / isNotUsedWhenEmpty correctly drop facades with no public members.
A non-nullable value class, or a plain internal fun f(a: Int, b: Int = 0), both produce an empty dump.
Failing test
eygraber@51db957
expected to be empty
but was: [..., public static synthetic fun sat-WUG17-E$default (Lcom/example/PathParent;ILjava/lang/Object;)Z, }]
Plugin version
3.18.0
Gradle version
9.6.0
JDK version
26
Kotlin and Kotlin Gradle Plugin (KGP) version
2.4.10
Describe the bug
When an
internaltop-level function has a default argument whose type is an inline value class wrapping a nullable reference, the function's synthetic$defaultbridge is treated as public API. That keeps the file facade in the ABI, sopublicTypeUsagereports aFooKtclass even though every top-level member in the file isinternalorprivate. There's no Kotlin syntax to restrict a facade, so the finding is unactionable.To Reproduce
lib:consumer(depends onlib), where every top-level member isinternal:Expected behavior
consumer/build/reports/dependency-analysis/main/abi-dump.txtis empty.Actual behavior
The function itself is correctly excluded; only the bridge leaks.
Additional context
MethodBinarySignature.alternateDefaultSignaturereconstructs the non-bridge signature to look up the real function's visibility in Kotlin metadata, but the bridge's descriptor carries the boxed wrapper type while metadata records the unboxed underlying type:A value class over a non-nullable reference erases to its underlying type so this works. Over a nullable reference it must stay boxed, so they diverge, the lookup misses, and
isEffectivelyPublic's?: truefallback treats the bridge as public.Note the facade handling itself is working.
isFileOrMultipartFacade()/isNotUsedWhenEmptycorrectly drop facades with no public members.A non-nullable value class, or a plain
internal fun f(a: Int, b: Int = 0), both produce an empty dump.Failing test
eygraber@51db957