-
Bug
-
Resolution: Unresolved
-
P4
-
None
-
None
In the following example, using the compiler Tree API to retrieve the type of a new class creation expression returns a type that is missing type annotations.
The expected output is:
Tree: new Test<@TA String>(), Type: Test<java.lang.@TA String>
(Also note that the returned type has an extra space in it: 'Test<java.lang. String>', because type annotation metadata is being attached but the list of annotations is empty.)
I think the issue is that the logic between these two locations is inconsistent:
https://github.com/openjdk/jdk/blob/7c22b814d670deda6c2bb93b1e150975c27a165f/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Annotate.java#L1162-L1164
https://github.com/openjdk/jdk/blob/7c22b814d670deda6c2bb93b1e150975c27a165f/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java#L5242-L5243
Annotate special-cases _anonymous_ new class creation expressions, because those type annotations have special handling to move them to the superclass of the anonymous class. Attr special-cases all new class creation expressions, not just anonymous ones, and 'annotateTypeSecondStage' never gets called for the non-anonymous case.
```
import com.sun.source.tree.NewClassTree;
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.source.util.Trees;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Locale;
import javax.lang.model.type.TypeMirror;
import javax.tools.JavaCompiler;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
public final class Z {
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 visitNewClass(NewClassTree tree, Void unused) {
TypeMirror type = Trees.instance(task).getTypeMirror(getCurrentPath());
System.err.printf("Tree: %s, Type: %s\n", tree, type);
return super.visitNewClass(tree, unused);
}
}.scan(e.getCompilationUnit(), null);
}
}
});
task.analyze();
}
}
```
```
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
class Test<T> {
@Target(ElementType.TYPE_USE)
@Retention(RetentionPolicy.RUNTIME)
@interface TA {}
public void testMethod() {
new Test<@TA String>();
}
}
```
$ java -fullversion
openjdk full version "25-ea+12-1311"
$ java Z Test.java
Tree: new Test<@TA String>(), Type: Test<java.lang. String>
The expected output is:
Tree: new Test<@TA String>(), Type: Test<java.lang.@TA String>
(Also note that the returned type has an extra space in it: 'Test<java.lang. String>', because type annotation metadata is being attached but the list of annotations is empty.)
I think the issue is that the logic between these two locations is inconsistent:
https://github.com/openjdk/jdk/blob/7c22b814d670deda6c2bb93b1e150975c27a165f/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Annotate.java#L1162-L1164
https://github.com/openjdk/jdk/blob/7c22b814d670deda6c2bb93b1e150975c27a165f/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java#L5242-L5243
Annotate special-cases _anonymous_ new class creation expressions, because those type annotations have special handling to move them to the superclass of the anonymous class. Attr special-cases all new class creation expressions, not just anonymous ones, and 'annotateTypeSecondStage' never gets called for the non-anonymous case.
```
import com.sun.source.tree.NewClassTree;
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.source.util.Trees;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Locale;
import javax.lang.model.type.TypeMirror;
import javax.tools.JavaCompiler;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
public final class Z {
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 visitNewClass(NewClassTree tree, Void unused) {
TypeMirror type = Trees.instance(task).getTypeMirror(getCurrentPath());
System.err.printf("Tree: %s, Type: %s\n", tree, type);
return super.visitNewClass(tree, unused);
}
}.scan(e.getCompilationUnit(), null);
}
}
});
task.analyze();
}
}
```
```
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
class Test<T> {
@Target(ElementType.TYPE_USE)
@Retention(RetentionPolicy.RUNTIME)
@interface TA {}
public void testMethod() {
new Test<@TA String>();
}
}
```
$ java -fullversion
openjdk full version "25-ea+12-1311"
$ java Z Test.java
Tree: new Test<@TA String>(), Type: Test<java.lang. String>
- links to
-
Review(master) openjdk/jdk/23946