immutables: Documented method of using Wrapper types fails with Java 17 library target

I have created a minimal reproduction of this issue here:

https://github.com/mglazer/repro-javaseventeen-immutables

The crux of the issue, is that when using Java 17 as a library target, if you have a Wrapper type, using:

@Value.Style(
        typeAbstract = "*Wrapper",
        typeImmutable = "*",
        visibility = Value.Style.ImplementationVisibility.PUBLIC,
        defaults = @Value.Immutable(builder = false, copy = false))
public @interface Wrapped {}

and:

import com.fasterxml.jackson.annotation.JsonValue;
import org.immutables.value.Value;

public abstract class Wrapper<T> {
    @JsonValue
    @Value.Parameter
    abstract T value();

    @Override
    public final String toString() {
        return value().toString();
    }
}

And then attempt to use that construction, only with Java 17 will you get compilation errors:

> Task :repro-javaseventeen-immutables-core:compileJava FAILED
Round 1:
	input files: {com.palantir.reprojavaseventeenimmutables.UseRepro, com.palantir.reprojavaseventeenimmutables.Wrapper, com.palantir.reprojavaseventeenimmutables.Wrapped, com.palantir.reprojavaseventeenimmutables.ReproIssueWrapper}
	annotations: [org.immutables.value.Value.Immutable, com.fasterxml.jackson.annotation.JsonValue, org.immutables.value.Value.Parameter, java.lang.Override, org.immutables.value.Value.Style, com.palantir.reprojavaseventeenimmutables.Wrapped]
	last round: false
Processor org.gradle.api.internal.tasks.compile.processing.TimeTrackingProcessor matches [/org.immutables.value.Value.Immutable] and returns false.
Processor org.gradle.api.internal.tasks.compile.processing.SupportedOptionsCollectingProcessor matches [/com.palantir.reprojavaseventeenimmutables.Wrapped, /com.fasterxml.jackson.annotation.JsonValue, java.base/java.lang.Override, /org.immutables.value.Value.Immutable, /org.immutables.value.Value.Parameter, /org.immutables.value.Value.Style] and returns false.
warning: Was unable to read source file for com.palantir.reprojavaseventeenimmutables.UseRepro[com.sun.tools.javac.code.Symbol$ClassSymbol.class]: java.io.FileNotFoundException: com/palantir/reprojavaseventeenimmutables/UseRepro.java
Round 2:
	input files: {com.palantir.reprojavaseventeenimmutables.ImmutableUseRepro, com.palantir.reprojavaseventeenimmutables.ReproIssue}
	annotations: [org.immutables.value.Generated, java.lang.SuppressWarnings, javax.annotation.ParametersAreNonnullByDefault, javax.annotation.processing.Generated, javax.annotation.concurrent.Immutable, javax.annotation.CheckReturnValue, javax.annotation.concurrent.NotThreadSafe, javax.annotation.Nullable, com.google.errorprone.annotations.CanIgnoreReturnValue, java.lang.Override]
	last round: false
warning: Was unable to read source file for com.palantir.reprojavaseventeenimmutables.UseRepro[com.sun.tools.javac.code.Symbol$ClassSymbol.class]: java.io.FileNotFoundException: com/palantir/reprojavaseventeenimmutables/UseRepro.java

Processor org.gradle.api.internal.tasks.compile.processing.TimeTrackingProcessor matches [] and returns false.
Processor org.gradle.api.internal.tasks.compile.processing.SupportedOptionsCollectingProcessor matches [/javax.annotation.concurrent.NotThreadSafe, /javax.annotation.Nullable, /com.google.errorprone.annotations.CanIgnoreReturnValue, /javax.annotation.CheckReturnValue, java.compiler/javax.annotation.processing.Generated, /javax.annotation.concurrent.Immutable, /org.immutables.value.Generated, java.base/java.lang.SuppressWarnings, java.base/java.lang.Override, /javax.annotation.ParametersAreNonnullByDefault] and returns false.
Round 3:
	input files: {}
	annotations: []
	last round: true
error: warnings found and -Werror specified
1 error
1 warning
error: warnings found and -Werror specified

The most noticeable error which occurs when verbose processor logging is disabled is the:

warning: Was unable to read source file for com.palantir.reprojavaseventeenimmutables.UseRepro[com.sun.tools.javac.code.Symbol$ClassSymbol.class]: java.io.FileNotFoundException: com/palantir/reprojavaseventeenimmutables/UseRepro.java

Otherwise, I have been unable to find anything that sticks out from the compilation messages.

I have attempted this with both Immutables 2.8.8 and 2.9.0 and it reproduces on both.

About this issue

  • Original URL
  • State: open
  • Created 2 years ago
  • Reactions: 3
  • Comments: 16 (5 by maintainers)

Commits related to this issue

Most upvoted comments

Oh gosh, I see the difference between the command-line and javac cases: running within idea, we’re missing some exports that are necessary for immutables, so despite both using javac, running within idea uses the SourceExtractor.DefaultExtractor while on the command line we get the SourceExtractor.JavacSourceExtractor` due to:

java.lang.IllegalAccessError: class org.immutables.value.internal.$generator$.$Compiler (in unnamed module @0x732ce05b) cannot access class com.sun.tools.javac.code.Symbol$ClassSymbol (in module jdk.compiler) because module jdk.compiler does not export com.sun.tools.javac.code to unnamed module @0x732ce05b
	at org.immutables.value.internal.$generator$.$Compiler.detectCompilers($Compiler.java:38)
	at org.immutables.value.internal.$generator$.$Compiler.<clinit>($Compiler.java:31)
	..etc

Why does this only occur in the IDE? A gradle plugin in my default template uses error-prone static analysis, which requires some compiler exports to function, which allow the SourceExtractor code to work correctly in jdk-17. 🌶️ The plugin doesn’t apply error-prone when it detects intellij for historical reasons 🌶️

I’ve pushed a commit to my repro which opts out of error-prone, allowing a repro from the command line. I suspect we may want to bring back the compiler warning where we can recommend adding exports to support the annotation processor.

The using TwoTuple is a key difference. When compiling One {... TwoTuple issue(), the class/interface TwoTuple is known to compiler, it knows the package it comes from, full name/path etc. In the generated code all imports are processed properly. But when using One {... Two issue(), the compiler says to us (to Immutables processor), “— look, here’s some broken and not available type called Two, I wash my hands”, then processor says to itself, “— Oki-doki, we’ll use this type anyway in case it will be generated, but if it comes from another package we’re busted, only if there would be source code available, we could try to parse imports from there…”, and the annotation processor uses ~dark magic~ javac/ecj specific ways to access the underlying source code and if it succeeds and founds import from another package for Two it will put such an import into generated file, but if it’s not – then type name Two copied into the generated code as is (if it’s happens to be generated in the same package – that will work, but will fail for another package).

Sorry for joining the discussion only now. I’m trying to understand if something can be done here aside any workarounds. I would totally agree that avoiding using not-yet-generated types is a good thing and we need to check if documentation is sufficiently clear on that, but sometimes there some processor code fixes/improvements can be made (as some other issues shown). Tried to use repro, but for some reason when java 17 is used (no workarounds and no change to 11, only change is Retention.SOURCE for Wrapped so no compile error) I cannot reproduce the issue. Not sure what am I doing wrong 🤷‍♂️

$ echo $JAVA_HOME
/Library/Java/JavaVirtualMachines/zulu-17.jdk/Contents/Home
$ java -version
openjdk version "17.0.1" 2021-10-19 LTS
OpenJDK Runtime Environment Zulu17.30+15-CA (build 17.0.1+12-LTS)
OpenJDK 64-Bit Server VM Zulu17.30+15-CA (build 17.0.1+12-LTS, mixed mode, sharing)
$ ./gradlew compileJava

> Task :repro-javaseventeen-immutables-core:compileJava
Round 1:
        input files: {com.palantir.reprojavaseventeenimmutables.ReproIssueWrapper, com.palantir.reprojavaseventeenimmutables.Wrapper, com.palantir.reprojavaseventeenimmutables.UseRepro, com.palantir.reprojavaseventeenimmutables.Wrapped}
        annotations: [org.immutables.value.Value.Immutable, com.palantir.reprojavaseventeenimmutables.Wrapped, com.fasterxml.jackson.annotation.JsonValue, org.immutables.value.Value.Parameter, java.lang.Override, java.lang.annotation.Retention, org.immutables.value.Value.Style]
        last round: false
Processor org.gradle.api.internal.tasks.compile.processing.TimeTrackingProcessor matches [/org.immutables.value.Value.Immutable] and returns false.
Processor org.gradle.api.internal.tasks.compile.processing.SupportedOptionsCollectingProcessor matches [/com.palantir.reprojavaseventeenimmutables.Wrapped, /com.fasterxml.jackson.annotation.JsonValue, java.base/java.lang.Override, /org.immutables.value.Value.Immutable, /org.immutables.value.Value.Parameter, java.base/java.lang.annotation.Retention, /org.immutables.value.Value.Style] and returns false.
Round 2:
        input files: {com.palantir.reprojavaseventeenimmutables.ReproIssue, com.palantir.reprojavaseventeenimmutables.ImmutableUseRepro}
        annotations: [org.immutables.value.Generated, java.lang.SuppressWarnings, javax.annotation.ParametersAreNonnullByDefault, javax.annotation.processing.Generated, javax.annotation.concurrent.Immutable, javax.annotation.CheckReturnValue, java.lang.Override, javax.annotation.Nullable, javax.annotation.concurrent.NotThreadSafe, com.google.errorprone.annotations.CanIgnoreReturnValue]
        last round: false
Processor org.gradle.api.internal.tasks.compile.processing.TimeTrackingProcessor matches [] and returns false.
Processor org.gradle.api.internal.tasks.compile.processing.SupportedOptionsCollectingProcessor matches [/javax.annotation.concurrent.NotThreadSafe, /javax.annotation.Nullable, /com.google.errorprone.annotations.CanIgnoreReturnValue, /javax.annotation.CheckReturnValue, java.compiler/javax.annotation.processing.Generated, /javax.annotation.concurrent.Immutable, /org.immutables.value.Generated, java.base/java.lang.SuppressWarnings, java.base/java.lang.Override, /javax.annotation.ParametersAreNonnullByDefault] and returns false.
Round 3:
        input files: {}
        annotations: []
        last round: true

BUILD SUCCESSFUL in 3s
3 actionable tasks: 2 executed, 1 up-to-date

Note that as described in #1323 by @Magotchi here, this can be worked around in gradle using the following, which unfortunately also disables incremental compilation:

compileJava {
    options.sourcepath = files(sourceSets.findByName("main").allJava.srcDirs)
}