autonomousapps / autonomousapps/dependency-analysis-gradle-plugin
`publicTypeUsage` reports a file facade whose members are all `internal`
- Dominant language
- Kotlin
- Stars
- 2.2k
- Forks
- 158
- Avg merge
- 16h 25m
- Merged PRs (30d)
- 46
Description
**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`:
```kotlin
@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`:
```kotlin
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
https://github.com/eygraber/dependency-analysis-gradle-plugin/commit/51db957efb3d067f4bbfe39953d1275af0dae7b7
```
expected to be empty
but was: [..., public static synthetic fun sat-WUG17-E$default (Lcom/example/PathParent;ILjava/lang/Object;)Z, }]
```
Contributor guide
Research direction
Start at MethodBinarySignature.alternateDefaultSignature and inspect how it is used by isEffectivelyPublic, using the boxed and unboxed descriptors described in the issue. Reproduce the failing test from commit 51db957efb3d067f4bbfe39953d1275af0dae7b7 and verify that the consumer ABI dump is empty while facade handling remains unchanged.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- kotlin
- Domain
- build-system
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 68/100