Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

File-level private vals of a type from an external dependency result in that dependency requiring to be an api dependency #1172

Open
travisMiehm opened this issue Apr 19, 2024 · 3 comments
Labels
bug Something isn't working toolchain:kotlin
Milestone

Comments

@travisMiehm
Copy link

Plugin version
1.31.0

Gradle version
8.7

(Optional) Kotlin and Kotlin Gradle Plugin (KGP) version
Kotlin 1.9.23

(Optional) reason output for bugs relating to incorrect advice

Source: main
------------
* Exposes 1 class: some.externa.dependency.ExternalEnum (implies api).

Describe the bug
When declaring file-level private val variables which are typed with some type from some dependency, that dependency is required to be an api() dependency, even though that file-level variable is only used in non-public contexts, such as a constructor argument to an internal class

private val FOO_ENUMS = ExternalEnum.entries.filter { it.name.startsWith("Foo") }

internal class MyClass(private val foos: Set<ExternalEnum> = FOO_ENUMS)

To Reproduce
Steps to reproduce the behavior:
In on project, declare some type

enum class ExternalEnum {
  FOO_ONE
  FOO_TWO
  BAR
}

In a second project, declare a class and a file-level private val variable

private val FOO_ENUMS = ExternalEnum.entries.filter { it.name.startsWith("Foo") }

internal class MyClass(private val foos: Set<ExternalEnum> = FOO_ENUMS)

Now run
./gradlew :second-project:reason --id :first-project

Expected behavior
The output of :reason should be that ExternalEnum is a used class, implying implementation

Additional context
An existing work around is to move the file-level private val variables to the companion object of the class, eg

internal class MyClass(private val foos: Set<ExternalEnum> = FOO_ENUMS) {
  companion object {
    private val FOO_ENUMS = ExternalEnum.entries.filter { it.name.startsWith("Foo") }
  }
}

This class will result in the command giving the expected output.

@autonomousapps autonomousapps added bug Something isn't working toolchain:kotlin labels Apr 20, 2024
@autonomousapps autonomousapps added this to the next milestone Apr 20, 2024
@autonomousapps
Copy link
Owner

Thanks for the report.

@autonomousapps
Copy link
Owner

Interestingly enough, this only happens with the exact setup you have. If your internal class instead looks like
internal class MyClass(private val foos: List<ExternalEnum>)
then DAGP doesn't advise changing the dependency declaration. I think there's some kind of synthetic bridge class that is actually public in the bytecode. Investigating...

@autonomousapps
Copy link
Owner

I created another repro in my project and decompiled it to Java. Here's what it looks like. You can clearly see the public methods that "expose" the private property.

// DeleteMe.java
package com.autonomousapps;

import kotlin.Metadata;
import kotlin.jvm.internal.DefaultConstructorMarker;
import kotlin.jvm.internal.Intrinsics;
import okio.Buffer;
import org.jetbrains.annotations.NotNull;

@Metadata(
   mv = {1, 8, 0},
   k = 1,
   xi = 48,
   d1 = {"\u0000\u0012\n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0000\n\u0002\u0018\u0002\n\u0002\b\u0002\b\u0000\u0018\u00002\u00020\u0001B\u000f\u0012\b\b\u0002\u0010\u0002\u001a\u00020\u0003¢\u0006\u0002\u0010\u0004R\u000e\u0010\u0002\u001a\u00020\u0003X\u0082\u0004¢\u0006\u0002\n\u0000¨\u0006\u0005"},
   d2 = {"Lcom/autonomousapps/DeleteMe;", "", "okio", "Lokio/Buffer;", "(Lokio/Buffer;)V", "dependency-analysis-gradle-plugin"}
)
public final class DeleteMe {
   @NotNull
   private final Buffer okio;

   public DeleteMe(@NotNull Buffer okio) {
      Intrinsics.checkNotNullParameter(okio, "okio");
      super();
      this.okio = okio;
   }

   // $FF: synthetic method
   public DeleteMe(Buffer var1, int var2, DefaultConstructorMarker var3) {
      if ((var2 & 1) != 0) {
         var1 = DeleteMeKt.access$getOKIO$p();
      }

      this(var1);
   }

   public DeleteMe() {
      this((Buffer)null, 1, (DefaultConstructorMarker)null);
   }
}
// DeleteMeKt.java
package com.autonomousapps;

import kotlin.Metadata;
import okio.Buffer;
import org.jetbrains.annotations.NotNull;

@Metadata(
   mv = {1, 8, 0},
   k = 2,
   xi = 48,
   d1 = {"\u0000\b\n\u0000\n\u0002\u0018\u0002\n\u0000\"\u000e\u0010\u0000\u001a\u00020\u0001X\u0082\u0004¢\u0006\u0002\n\u0000¨\u0006\u0002"},
   d2 = {"OKIO", "Lokio/Buffer;", "dependency-analysis-gradle-plugin"}
)
public final class DeleteMeKt {
   @NotNull
   private static final Buffer OKIO = new Buffer();

   // $FF: synthetic method
   public static final Buffer access$getOKIO$p() {
      return OKIO;
   }
}

It might be possible to detect this and provide more accurate advice. Might also be non-trivial.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working toolchain:kotlin
Projects
None yet
Development

No branches or pull requests

2 participants