autonomousapps / autonomousapps/dependency-analysis-gradle-plugin

Incorrect advice given if a type is used as type parameter of a generic type in specific scenarios

Open
#1,442 1 comment 0 reactions 0 assignees View on GitHub
bug toolchain:java toolchain:kotlin
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.