autonomousapps / autonomousapps/dependency-analysis-gradle-plugin

KMP: invisible inline-member usage in `commonMain`

Open
#1,791 1 comment 0 reactions 0 assignees View on GitHub
bug toolchain:kmp
Dominant language
Kotlin
Stars
2.2k
Forks
158
Avg merge
16h 25m
Merged PRs (30d)
46

Description

**Plugin version**
3.17.0

**Gradle version**
9.6.1

**JDK version**
26

**Kotlin and Kotlin Gradle Plugin (KGP) version**
2.4.10

**Android Gradle Plugin (AGP) version**
9.3.0

**`reason` output for bugs relating to incorrect advice**
```
------------------------------------------------------------
You asked about the dependency 'com.michael-bull.kotlin-result:kotlin-result-coroutines:2.3.1 (libs.kotlin.result.coroutines)'.
You have been advised to remove this dependency from 'commonMainImplementation'.
------------------------------------------------------------

Shortest path from :core:database to com.michael-bull.kotlin-result:kotlin-result-coroutines:2.3.1 (libs.kotlin.result.coroutines) for androidCompileClasspath:
:core:database
\--- com.michael-bull.kotlin-result:kotlin-result-coroutines:2.3.1

Source: androidMain
-------------------
(no usages)
```

Following the advice fails compilation:

```
e: file://.../core/database/src/commonMain/kotlin/.../DefaultLocalDataSource.kt:9:38 Unresolved reference 'coroutines'.
e: file://.../core/database/src/commonMain/kotlin/.../DefaultLocalDataSource.kt:51:16 Unresolved reference 'runSuspendCatching'.
```

**Describe the bug**
In a KMP module, a `commonMain` dependency whose only used API is an `inline` function is reported as unused, even though removing it breaks compilation.

The module has in `commonMain`:

```kotlin
import com.github.michaelbull.result.coroutines.runSuspendCatching
```

`runSuspendCatching` is `inline`, which it seems like DAGP handles with import matching (`FindKotlinMagicTask`, `ComputeUsagesTask.usesInlineMember`), which compares the project's source imports against inline members found in dependency jars.

I did some digging and the producer half works. `intermediates/inline-usage.json` for the `androidMain` analysis correctly lists the artifact's inline members:

```json
{
"coordinates": { "identifier": "com.michael-bull.kotlin-result:kotlin-result-coroutines-jvm", ... },
"inlineMembers": [
{ "className": "com.github.michaelbull.result.coroutines.RunSuspendCatchingKt",
"packageName": "com.github.michaelbull.result.coroutines",
"inlineMembers": ["runSuspendCatching"] }, ...
]
}
```

The consumer half never sees the import. `intermediates/exploded-source.json` for the same analysis contains only the `androidMain` file:

```json
[ { "relativePath": "src/androidMain/kotlin/.../DatabaseTypeFactory.android.kt", ... } ]
```

The `commonMain` file containing the `runSuspendCatching` import is absent, so `usesInlineMember` finds no matching import and the dependency falls through to `Bucket.NONE`.

The cause appears to be in source collection for KMP compilations. `KmpSourceSet` uses only the compilation's default source set:

```kotlin
// internal/analyzer/JvmSourceSet.kt
override val sourceCode: SourceDirectorySet = compilation.defaultSourceSet.kotlin
```

For the Android compilation of a KMP module, `defaultSourceSet` is `androidMain`; the `dependsOn` closure (`commonMain`, `nativeMain`, etc.) is not included. So any usage evidence that exists only in common source (e.g. inline function imports) is invisible to every per-compilation analysis. (`KotlinCommonSources.kt` collects `commonMain`/`commonTest` sources, but its only caller is the unused `ListSourceFilesTask`.)

**To Reproduce**
1. Create a KMP library module with an Android target.
2. In `commonMain`, declare `implementation("com.michael-bull.kotlin-result:kotlin-result-coroutines:2.3.1")`.
3. In `commonMain` source, call `runSuspendCatching { ... }` (its result must flow into non-inline API from the base `kotlin-result` artifact, which is typical).
4. Run `buildHealth`: the dependency is advised for removal from `commonMainImplementation`.
5. Remove it: compilation fails with `Unresolved reference 'runSuspendCatching'`.

**Expected behavior**
The usage of the inline function is picked up.

**Additional context**
Workaround:

```kotlin
dependencyAnalysis {
issues {
all {
onUnusedDependencies {
exclude("com.michael-bull.kotlin-result:kotlin-result-coroutines")
}
}
}
}
```

Contributor guide

Open the contributing guide

Research direction

Start with internal/analyzer/JvmSourceSet.kt, especially KmpSourceSet's sourceCode, then read KotlinCommonSources.kt and its callers. Reproduce the KMP example and run buildHealth to trace why commonMain imports are absent from exploded-source.json. Done means the inline usage is detected and the dependency is no longer incorrectly reported as unused.

Written by the indexing model from the issue text.

Assessment

Tech stack
android, kotlin
Domain
build-system
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
55/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.