autonomousapps / autonomousapps/dependency-analysis-gradle-plugin
Incorrect advice given if a type is used as type parameter of a generic type in specific scenarios
- Dominant language
- Kotlin
- Stars
- 2.2k
- Forks
- 158
- Avg merge
- 16h 25m
- Merged PRs (30d)
- 46
Description
**Plugin version**
2.17.0
**Gradle version**
8.12
**JDK version**
21
**Kotlin and Kotlin Gradle Plugin (KGP) version**
2.1.20
**`reason` output for bugs relating to incorrect advice**
```
------------------------------------------------------------
You asked about the dependency ':moduleA'.
You have been advised to change this dependency to 'implementation' from 'api'.
------------------------------------------------------------
Shortest path from :moduleB to :moduleA for compileClasspath:
:moduleB
\--- :moduleA
Shortest path from :moduleB to :moduleA for runtimeClasspath:
:moduleB
\--- :moduleA
Shortest path from :moduleB to :moduleA for testCompileClasspath:
:moduleB
\--- :moduleA
Shortest path from :moduleB to :moduleA for testRuntimeClasspath:
:moduleB
\--- :moduleA
Source: main
------------
* Imports 1 class: de.exaring.a.Ui (implies implementation).
Source: test
------------
(no usages)
```
**Describe the bug**
We have a module that defines a `Ui` type:
```kotlin
interface Ui
```
And another module exposes that type by using it as a generic type parameter on a public return type, like that:
```kotlin
interface UiModule {
fun bindUiMap(): Map, Ui>
}
```
The plugin doesn't recognize `Ui` as being part of the public API and gives the (incorrect) advice to change the dependency from `api` to `implementation`:
```
Existing dependencies which should be modified to be as indicated:
implementation(project(":ui:api")) (was api)
```
Now the interesting part. I've investigated this further and found out that the wrong advice is only given in a very specific scenario:
* The generic type has multiple type parameters (like `Map` or `Pair` or `Triple`)
* There is another type parameter which is itself a generic type
* That other type parameter is listed **before** our type in question
This is best demonstrated with a few examples:
```kotlin
interface UiModule {
// These trigger an incorrect advice:
fun example1(): Map, Ui>
fun example2(): Pair, Ui>
fun example3(): Triple, String, Ui>
fun example4(): Triple, List, Ui>
fun example5(): Triple, Ui, String>
fun example6(): Triple, Ui>
// These don't trigger an incorrect advice:
fun example7(): Map>
fun example8(): Map, List>
fun example9(): Map
fun example10(): Triple, String>
fun example11(): Triple, Ui>
}
```
**To Reproduce**
Steps to reproduce the behavior:
Here is a simple reproducer project: [dagp-generics-repro.zip](https://github.com/user-attachments/files/20164181/dagp-generics-repro.zip)
Running `buildHealth` on this project will produce the incorrect advice with the `reason` output given above.
**Expected behavior**
The plugin should not give any advice to change the dependency to `implementation`, since the type is exposed in the public API via the generic type parameter.
Contributor guide
Research direction
Start with the linked dagp-generics-repro.zip project and run buildHealth using the versions listed in the report. Trace the dependency-analysis path that produces the reason output for the Map, Pair, and Triple examples, comparing cases where nested generic parameters appear before or after Ui. Done means the public generic usages no longer produce advice to change api to implementation, while the reproducer remains covered.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- kotlin
- Domain
- build-system
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Clearly specified
- Newbie friendliness
- 35/100