Skip to content

publicTypeUsage reports a file facade whose members are all internal #1801

Description

@eygraber

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, }]

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions