Skip to content

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

@timoloewe

Description

@timoloewe

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:

interface Ui

And another module exposes that type by using it as a generic type parameter on a public return type, like that:

interface UiModule {
    fun bindUiMap(): Map<Class<out Any>, 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:

interface UiModule {
    // These trigger an incorrect advice:
    fun example1(): Map<List<String>, Ui>
    fun example2(): Pair<List<String>, Ui>
    fun example3(): Triple<List<String>, String, Ui>
    fun example4(): Triple<List<String>, List<String>, Ui>
    fun example5(): Triple<List<String>, Ui, String>
    fun example6(): Triple<String, List<String>, Ui>

    // These don't trigger an incorrect advice:
    fun example7(): Map<Ui, List<String>>
    fun example8(): Map<List<String>, List<Ui>>
    fun example9(): Map<String, Ui>
    fun example10(): Triple<Ui, List<String>, String>
    fun example11(): Triple<Ui, List<String>, Ui>
}

To Reproduce
Steps to reproduce the behavior:

Here is a simple reproducer project: 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.

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions