RFR: 8370800: Downgrade cant.attach.type.annotations diagnostics to warnings

Jan Lahoda jlahoda at openjdk.org
Wed Oct 29 07:55:01 UTC 2025


On Tue, 28 Oct 2025 11:41:01 GMT, Liam Miller-Cushon <cushon at openjdk.org> wrote:

> Hi, please consider this fix for [JDK-8370800: Downgrade cant.attach.type.annotations diagnostics to warnings](https://bugs.openjdk.org/browse/JDK-8370800).
> 
> As discussed in the, this reduces the compatibility impact of these diagnostics for builds that deliberately omit transitive annotation dependencies, for example if they are only referenced through javadoc `@link` tags, or by frameworks that conditionally load the classes.
> 
> The PR changes the existing error diagnostic to an unconditional warning. Another alternative would be to make it an optional xlint diagnostic, perhaps as part of `-Xlint:classfile`, or as another category.

So, overall, I am not convinced this is a good move. Yes, we have some existing cases where missing stuff produces just warnings in the class reader, but these are cases where annotations, or their attributes, are missing. Not when the actual field/method type is missing. I.e. in the test case, not producing an error for missing `@Anno` would seem more or less OK to me, but ignoring errors for missing type `A` makes much less sense to me.

But, even if we decided to ignore the missing class error, the implementation is, sadly, wrong. We cannot just ignore the `CompletionFailure`, as that will never be thrown again for the given `ClassSymbol`. And then javac may proceed to generate a classfile for a broken input. For example, changing the test to:

    void testMissingEnclosingType() throws Exception {
        String annoSrc =
                """
                import static java.lang.annotation.ElementType.TYPE_USE;
                import java.lang.annotation.Target;
                @Target(TYPE_USE)
                @interface Anno {}

                class A<E> {}

                class B {
                  public @Anno A<String> a;
                }
                """;
        String cSrc =
                """
                class C {
                  B b;
                  public void test() {
                      b.a.toString();
                  }
                }
                """;

        Path base = Paths.get(".");
        Path src = base.resolve("src");
        tb.createDirectories(src);
        tb.writeJavaFiles(src, annoSrc, cSrc);
        Path out = base.resolve("out");
        tb.createDirectories(out);
        new JavacTask(tb).outdir(out).files(tb.findJavaFiles(src)).run();

        // now if we remove A.class javac should not crash
        tb.deleteFiles(out.resolve("A.class"));

        List<String> log =
                new JavacTask(tb)
                        .outdir(out)
                        .classpath(out)
                        .options(/*"-Werror", */"-XDrawDiagnostics")
                        .files(src.resolve("C.java"))
                        .run(Expect.FAIL)
                        .writeAll()
                        .getOutputLines(Task.OutputKind.DIRECT);

        var expectedOutput =
                List.of(
                        "B.class:-:-: compiler.warn.cant.attach.type.annotations: @Anno, B, a,"
                                + " (compiler.misc.class.file.not.found: A)",
                        "- compiler.err.warnings.and.werror",
                        "1 error",
                        "1 warning");
        if (!expectedOutput.equals(log)) {
            throw new Exception("expected output not found: " + log);
        }
    }

leads to:

An exception has occurred in the compiler (26-internal). Please file a bug against the Java compiler via the Java bug reporting page (https://bugreport.java.com) after checking the Bug Database (https://bugs.java.com) for duplicates. Include your program, the following diagnostic, and the parameters passed to the Java compiler in your report. Thank you.
java.lang.ClassCastException: class com.sun.tools.javac.code.Symbol$ClassSymbol cannot be cast to class com.sun.tools.javac.code.Symbol$MethodSymbol (com.sun.tools.javac.code.Symbol$ClassSymbol and com.sun.tools.javac.code.Symbol$MethodSymbol are in module jdk.compiler of loader 'app')
	at jdk.compiler/com.sun.tools.javac.comp.TransTypes.visitApply(TransTypes.java:931)
	at jdk.compiler/com.sun.tools.javac.tree.JCTree$JCMethodInvocation.accept(JCTree.java:1869)
	at jdk.compiler/com.sun.tools.javac.tree.TreeTranslator.translate(TreeTranslator.java:58)
	at jdk.compiler/com.sun.tools.javac.comp.TransTypes.translate(TransTypes.java:450)
...


I think that if you really want to ignore the `CompletionFailure`s at this point, `DeferredCompletionFailureHandler` needs to be used to re-set the `ClassSymbol` for `A` to the original state. `speculativeCodeHandler` might be usable for this (look how it is used in `DeferredAttr`). `b.a.toString();` in the above testcase would then hopefully produce a compile-time error correctly.

Second problem is that catching the `CompletionFailure` at this place may leave some of the annotations unassigned, leading to an inconsistent model. Like, what if the type of the field is `@Anno Triple<@Anno Integer, @Anno A, @Anno String>` (where `A` is missing) - I may get some of the types with the annotation, and some without, no? Shouldn't the annotations be applied consistently? (It is an issue even now, but now javac reports an error, so it is less of a problem if the model is sub-optimal.)

-------------

PR Comment: https://git.openjdk.org/jdk/pull/28018#issuecomment-3460238387


More information about the compiler-dev mailing list