Inconsistent handling of type updates in typeWithAnnotations

XMLWordPrintable

    • Type: Bug
    • Resolution: Unresolved
    • Priority: P4
    • None
    • Affects Version/s: None
    • Component/s: tools

      TypeAnnotations#typeWithAnnotations handles annotations in ambiguous contexts where based on the syntax it isn't clear if an annotation is a type or declaration annotation.

      e.g. for a local variable `@A String foo;`, the annotation could be a declaration or TYPE_USE annotation, or both, depending on its @Target. So the parser can't parse it into a JCAnnotatedType, and the disambiguation happens later in TypeAnnotations.

      typeWithAnnotations creates a new type with any type annotations that are present, and then stores it in a few places, including the corresponding symbol.

      That method is not consistent about whether it writes the updated type back to the AST. For example it updates the tree's type here [1], but there's a separate path for array types that doesn't update the AST [2].

      [1] https://github.com/openjdk/jdk/blob/7a7e7c9ae11cb124c14d5d2d3b7e2f5649205106/src/jdk.compiler/share/classes/com/sun/tools/javac/code/TypeAnnotations.java#L545-L547
      [2] https://github.com/openjdk/jdk/blob/7a7e7c9ae11cb124c14d5d2d3b7e2f5649205106/src/jdk.compiler/share/classes/com/sun/tools/javac/code/TypeAnnotations.java#L451-L452

      As far as I can tell this doesn't affect the compilation output (the type annotation attributes are emitted in both cases), or any supported public APIs. If I update typeWithAnnotations to never update typetree.type, it doesn't break any tier1 tests. Still, the inconsistency doesn't seem deliberate.

      It looks like the initial checkin of TypeAnnotations did update types on arrays:
      https://github.com/openjdk/jdk/commit/053a9d56cd7b74dd96eec6739729e6fa1fb81764#diff-ad849326def698586d764955559c0e8823ee1537ea8f99fea442ea577f11b166R437

      And I think that got lost in https://github.com/openjdk/jdk/commit/7d3885b23964b7d8e9b6e8f4a748625ac23cb387

      ---

      Here's an example demonstrating the difference:

      ===
      import com.sun.source.tree.VariableTree;
      import com.sun.source.util.JavacTask;
      import com.sun.source.util.TaskEvent;
      import com.sun.source.util.TaskListener;
      import com.sun.source.util.TreePathScanner;
      import com.sun.tools.javac.tree.JCTree.JCVariableDecl;

      import javax.tools.JavaCompiler;
      import javax.tools.StandardJavaFileManager;
      import javax.tools.ToolProvider;
      import java.io.IOException;
      import java.nio.charset.StandardCharsets;
      import java.util.Locale;

      public final class C {

        public static void main(final String[] args) throws IOException {
          final JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
          StandardJavaFileManager fileManager =
              compiler.getStandardFileManager(null, Locale.ENGLISH, StandardCharsets.UTF_8);
          final JavacTask task =
              (JavacTask)
                  compiler.getTask(null, null, null, null, null, fileManager.getJavaFileObjects(args));
          task.addTaskListener(
              new TaskListener() {
                @Override
                public void finished(TaskEvent e) {
                  if (e.getKind() == TaskEvent.Kind.ANALYZE) {
                    new TreePathScanner<Void, Void>() {
                      @Override
                      public Void visitVariable(VariableTree tree, Void unused) {
                        print((JCVariableDecl) tree);
                        return super.visitVariable(tree, unused);
                      }

                      private void print(JCVariableDecl tree) {
                        System.err.printf(
                            "%s\n Tree: %s\n Type: %s\n\n",
                            tree.getName(), tree.toString().replace("\n", " "), tree.vartype.type);
                      }
                    }.scan(e.getCompilationUnit(), null);
                  }
                }
              });
          task.analyze();
        }
      }
      ===

      ===
      import java.lang.annotation.ElementType;
      import java.util.List;
      import java.lang.annotation.Target;

      @Target(ElementType.TYPE_USE)
      @interface TA {}

      class Foo {

        void f() {
          @TA List<String>[] a;
          @TA List<String> b;
        }
      }
      ===

      tree.vartype.type has type annotations for 'b', but not for 'a' (which has an array type)

      $ java --add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED C.java Foo.java
      ...
      a
        Tree: @TA List<String>[] a
        Type: java.util.List<java.lang.String>[]

      b
        Tree: @TA List<String> b
        Type: java.util.@TA List<java.lang.String>

            Assignee:
            Unassigned
            Reporter:
            Liam Miller-Cushon
            Votes:
            0 Vote for this issue
            Watchers:
            1 Start watching this issue

              Created:
              Updated: