8371155: Type annotations on local variables are classified after the local var initializer has been type checked

Reviewed-by: vromero
This commit is contained in:
Jan Lahoda 2026-03-04 07:17:26 +00:00
parent 39b1e9d839
commit 58d2c1d47d
3 changed files with 283 additions and 27 deletions

View File

@ -50,6 +50,7 @@ import com.sun.tools.javac.code.TypeAnnotationPosition.TypePathEntryKind;
import com.sun.tools.javac.code.Symbol.VarSymbol;
import com.sun.tools.javac.code.Symbol.MethodSymbol;
import com.sun.tools.javac.code.Type.ModuleType;
import com.sun.tools.javac.code.Type.UnionClassType;
import com.sun.tools.javac.comp.Annotate;
import com.sun.tools.javac.comp.Attr;
import com.sun.tools.javac.comp.AttrContext;
@ -61,6 +62,7 @@ import com.sun.tools.javac.tree.JCTree.JCAnnotatedType;
import com.sun.tools.javac.tree.JCTree.JCAnnotation;
import com.sun.tools.javac.tree.JCTree.JCArrayTypeTree;
import com.sun.tools.javac.tree.JCTree.JCBlock;
import com.sun.tools.javac.tree.JCTree.JCCatch;
import com.sun.tools.javac.tree.JCTree.JCClassDecl;
import com.sun.tools.javac.tree.JCTree.JCExpression;
import com.sun.tools.javac.tree.JCTree.JCFieldAccess;
@ -70,6 +72,7 @@ import com.sun.tools.javac.tree.JCTree.JCMethodDecl;
import com.sun.tools.javac.tree.JCTree.JCMethodInvocation;
import com.sun.tools.javac.tree.JCTree.JCNewArray;
import com.sun.tools.javac.tree.JCTree.JCNewClass;
import com.sun.tools.javac.tree.JCTree.JCTry;
import com.sun.tools.javac.tree.JCTree.JCTypeApply;
import com.sun.tools.javac.tree.JCTree.JCTypeIntersection;
import com.sun.tools.javac.tree.JCTree.JCTypeParameter;
@ -111,6 +114,7 @@ public class TypeAnnotations {
final Symtab syms;
final Annotate annotate;
final Attr attr;
final Types types;
@SuppressWarnings("this-escape")
protected TypeAnnotations(Context context) {
@ -120,6 +124,7 @@ public class TypeAnnotations {
syms = Symtab.instance(context);
annotate = Annotate.instance(context);
attr = Attr.instance(context);
types = Types.instance(context);
}
/**
@ -132,7 +137,24 @@ public class TypeAnnotations {
annotate.afterTypes(() -> {
JavaFileObject oldSource = log.useSource(env.toplevel.sourcefile);
try {
new TypeAnnotationPositions(true).scan(tree);
new TypeAnnotationPositions(null, true).scan(tree);
} finally {
log.useSource(oldSource);
}
});
}
public void organizeTypeAnnotationsSignaturesForLocalVarType(final Env<AttrContext> env, final JCVariableDecl tree) {
annotate.afterTypes(() -> {
JavaFileObject oldSource = log.useSource(env.toplevel.sourcefile);
try {
TypeAnnotationPositions pos = new TypeAnnotationPositions(env.tree, true);
if (env.tree instanceof JCLambda) {
pos.push(env.tree);
} else {
pos.push(env.enclMethod);
}
pos.scan(tree);
} finally {
log.useSource(oldSource);
}
@ -155,7 +177,7 @@ public class TypeAnnotations {
* top-level blocks, and method bodies, and should be called from Attr.
*/
public void organizeTypeAnnotationsBodies(JCClassDecl tree) {
new TypeAnnotationPositions(false).scan(tree);
new TypeAnnotationPositions(null, false).scan(tree);
}
public enum AnnotationType { DECLARATION, TYPE, NONE, BOTH }
@ -265,9 +287,11 @@ public class TypeAnnotations {
private class TypeAnnotationPositions extends TreeScanner {
private final JCTree contextTree;
private final boolean sigOnly;
TypeAnnotationPositions(boolean sigOnly) {
TypeAnnotationPositions(JCTree contextTree, boolean sigOnly) {
this.contextTree = contextTree;
this.sigOnly = sigOnly;
}
@ -455,14 +479,15 @@ public class TypeAnnotations {
return type.annotatedType(onlyTypeAnnotations);
} else if (type.getKind() == TypeKind.UNION) {
// There is a TypeKind, but no TypeTag.
UnionClassType ut = (UnionClassType) type;
JCTypeUnion tutree = (JCTypeUnion)typetree;
JCExpression fst = tutree.alternatives.get(0);
Type res = typeWithAnnotations(fst, fst.type, annotations, onlyTypeAnnotations, pos);
fst.type = res;
// TODO: do we want to set res as first element in uct.alternatives?
// UnionClassType uct = (com.sun.tools.javac.code.Type.UnionClassType)type;
// Return the un-annotated union-type.
return type;
ListBuffer<Type> alternatives = new ListBuffer<>();
alternatives.add(res);
alternatives.addAll(ut.alternatives_field.tail);
return new UnionClassType((ClassType) ut.getLub(), alternatives.toList());
} else {
Type enclTy = type;
Element enclEl = type.asElement();
@ -1237,7 +1262,17 @@ public class TypeAnnotations {
} else if (tree.sym == null) {
Assert.error("Visiting tree node before memberEnter");
} else if (tree.sym.getKind() == ElementKind.PARAMETER) {
// Parameters are handled in visitMethodDef or visitLambda.
if (sigOnly) {
if (contextTree instanceof JCCatch c && c.param == tree) {
//exception "parameter":
final TypeAnnotationPosition pos =
TypeAnnotationPosition.exceptionParameter(currentLambda,
tree.pos);
separateAnnotationsKinds(tree, tree.vartype, tree.sym.type, tree.sym, pos);
} else {
// (real) parameters are handled in visitMethodDef or visitLambda.
}
}
} else if (tree.sym.getKind() == ElementKind.FIELD) {
if (sigOnly) {
TypeAnnotationPosition pos =
@ -1245,27 +1280,36 @@ public class TypeAnnotations {
separateAnnotationsKinds(tree, tree.vartype, tree.sym.type, tree.sym, pos);
}
} else if (tree.sym.getKind() == ElementKind.LOCAL_VARIABLE) {
final TypeAnnotationPosition pos =
TypeAnnotationPosition.localVariable(currentLambda,
tree.pos);
if (!tree.declaredUsingVar()) {
separateAnnotationsKinds(tree, tree.vartype, tree.sym.type, tree.sym, pos);
if (sigOnly && !tree.declaredUsingVar()) {
if (contextTree instanceof JCTry t && t.resources.contains(tree)) {
final TypeAnnotationPosition pos =
TypeAnnotationPosition.resourceVariable(currentLambda,
tree.pos);
separateAnnotationsKinds(tree, tree.vartype, tree.sym.type, tree.sym, pos);
} else {
final TypeAnnotationPosition pos =
TypeAnnotationPosition.localVariable(currentLambda,
tree.pos);
if (!tree.declaredUsingVar()) {
separateAnnotationsKinds(tree, tree.vartype, tree.sym.type, tree.sym, pos);
}
}
}
} else if (tree.sym.getKind() == ElementKind.BINDING_VARIABLE) {
final TypeAnnotationPosition pos =
TypeAnnotationPosition.localVariable(currentLambda,
tree.pos);
separateAnnotationsKinds(tree, tree.vartype, tree.sym.type, tree.sym, pos);
if (sigOnly) {
final TypeAnnotationPosition pos =
TypeAnnotationPosition.localVariable(currentLambda,
tree.pos);
separateAnnotationsKinds(tree, tree.vartype, tree.sym.type, tree.sym, pos);
}
} else if (tree.sym.getKind() == ElementKind.EXCEPTION_PARAMETER) {
final TypeAnnotationPosition pos =
TypeAnnotationPosition.exceptionParameter(currentLambda,
tree.pos);
separateAnnotationsKinds(tree, tree.vartype, tree.sym.type, tree.sym, pos);
if (sigOnly) {
Assert.error("Should not get variable kind: " + tree.sym.getKind());
}
} else if (tree.sym.getKind() == ElementKind.RESOURCE_VARIABLE) {
final TypeAnnotationPosition pos =
TypeAnnotationPosition.resourceVariable(currentLambda,
tree.pos);
separateAnnotationsKinds(tree, tree.vartype, tree.sym.type, tree.sym, pos);
if (sigOnly) {
Assert.error("Should not get variable kind: " + tree.sym.getKind());
}
} else if (tree.sym.getKind() == ElementKind.ENUM_CONSTANT) {
// No type annotations can occur here.
} else {

View File

@ -1281,6 +1281,7 @@ public class Attr extends JCTree.Visitor {
try {
annotate.blockAnnotations();
memberEnter.memberEnter(tree, env);
typeAnnotations.organizeTypeAnnotationsSignaturesForLocalVarType(env, tree);
} finally {
annotate.unblockAnnotations();
}
@ -4226,7 +4227,6 @@ public class Attr extends JCTree.Visitor {
} else {
type = resultInfo.pt;
}
tree.type = tree.var.type = type;
BindingSymbol v = new BindingSymbol(tree.var.mods.flags | tree.var.declKind.additionalSymbolFlags,
tree.var.name, type, env.info.scope.owner);
v.pos = tree.pos;
@ -4244,7 +4244,8 @@ public class Attr extends JCTree.Visitor {
annotate.queueScanTreeAndTypeAnnotate(tree.var.vartype, env, v);
}
annotate.flush();
result = tree.type;
typeAnnotations.organizeTypeAnnotationsSignaturesForLocalVarType(env, tree.var);
result = tree.type = tree.var.type = v.type;
if (v.isUnnamedVariable()) {
matchBindings = MatchBindingsComputer.EMPTY;
} else {

View File

@ -0,0 +1,211 @@
/*
* Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/*
* @test
* @bug 8371155
* @summary Verify type annotations on local-like variables are propagated to
* their types at an appropriate time.
* @library /tools/lib
* @modules
* jdk.compiler/com.sun.tools.javac.api
* jdk.compiler/com.sun.tools.javac.main
* @build toolbox.ToolBox toolbox.JavacTask
* @run main TypeAnnotationsOnVariables
*/
import com.sun.source.tree.LambdaExpressionTree;
import com.sun.source.tree.Tree;
import com.sun.source.tree.VariableTree;
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.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.type.UnionType;
import toolbox.JavacTask;
import toolbox.ToolBox;
public class TypeAnnotationsOnVariables {
public static void main(String... args) throws Exception {
new TypeAnnotationsOnVariables().run();
}
ToolBox tb = new ToolBox();
void run() throws Exception {
typeAnnotationInConstantExpressionFieldInit(Paths.get("."));
}
void typeAnnotationInConstantExpressionFieldInit(Path base) throws Exception {
Path src = base.resolve("src");
Path classes = base.resolve("classes");
tb.writeJavaFiles(src,
"""
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
import java.util.function.Supplier;
class Test {
@Target(ElementType.TYPE_USE)
@interface TypeAnno { }
@TypeAnno Supplier<String> r_f_i = () -> "r_f_i";
static @TypeAnno Supplier<String> r_f_s = () -> "r_f_s";
{
@TypeAnno Supplier<String> r_init_i = () -> "r_init_i";
}
static {
@TypeAnno Supplier<String> r_init_s = () -> "r_init_s";
}
void m() {
@TypeAnno Supplier<String> r_m_i = () -> "r_m_i";
}
static void g() {
@TypeAnno Supplier<String> r_g_s = () -> "r_g_s";
}
void h() {
t_cr(() -> "t_cr");
}
void i() {
t_no_cr((@TypeAnno Supplier<String>)() -> "t_no_cr");
}
void j() {
t_no_cr((java.io.Serializable & @TypeAnno Supplier<String>)() -> "t_no_cr");
}
void k() throws Throwable {
try (@TypeAnno AutoCloseable ac = () -> {}) {}
}
void l() {
try {
} catch (@TypeAnno Exception e1) {}
}
void n() {
try {
} catch (@TypeAnno final Exception e2) {}
}
void o() {
try {
} catch (@TypeAnno IllegalStateException | @TypeAnno NullPointerException | IllegalArgumentException e3) {}
}
void t_cr(@TypeAnno Supplier<String> r_p) { }
void t_no_cr(@TypeAnno Supplier<String> r_p) { }
}
""");
Files.createDirectories(classes);
List<String> actual = new ArrayList<>();
new JavacTask(tb)
.options("-d", classes.toString())
.files(tb.findJavaFiles(src))
.callback(task -> {
task.addTaskListener(new TaskListener() {
@Override
public void finished(TaskEvent e) {
if (e.getKind() != TaskEvent.Kind.ANALYZE) {
return ;
}
Trees trees = Trees.instance(task);
new TreePathScanner<Void, Void>() {
@Override
public Void visitVariable(VariableTree node, Void p) {
actual.add(node.getName() + ": " + typeToString(trees.getTypeMirror(getCurrentPath())));
return super.visitVariable(node, p);
}
@Override
public Void visitLambdaExpression(LambdaExpressionTree node, Void p) {
actual.add(treeToString(node)+ ": " + typeToString(trees.getTypeMirror(getCurrentPath())));
return super.visitLambdaExpression(node, p);
}
}.scan(e.getCompilationUnit(), null);
}
});
})
.run()
.writeAll();
List<String> expected = List.of(
"r_f_i: java.util.function.@Test.TypeAnno Supplier<java.lang.String>",
"()->\"r_f_i\": java.util.function.@Test.TypeAnno Supplier<java.lang.String>",
"r_f_s: java.util.function.@Test.TypeAnno Supplier<java.lang.String>",
"()->\"r_f_s\": java.util.function.@Test.TypeAnno Supplier<java.lang.String>",
"r_init_i: java.util.function.@Test.TypeAnno Supplier<java.lang.String>",
"()->\"r_init_i\": java.util.function.@Test.TypeAnno Supplier<java.lang.String>",
"r_init_s: java.util.function.@Test.TypeAnno Supplier<java.lang.String>",
"()->\"r_init_s\": java.util.function.@Test.TypeAnno Supplier<java.lang.String>",
"r_m_i: java.util.function.@Test.TypeAnno Supplier<java.lang.String>",
"()->\"r_m_i\": java.util.function.@Test.TypeAnno Supplier<java.lang.String>",
"r_g_s: java.util.function.@Test.TypeAnno Supplier<java.lang.String>",
"()->\"r_g_s\": java.util.function.@Test.TypeAnno Supplier<java.lang.String>",
"()->\"t_cr\": java.util.function.@Test.TypeAnno Supplier<java.lang.String>",
"()->\"t_no_cr\": java.util.function.@Test.TypeAnno Supplier<java.lang.String>",
"()->\"t_no_cr\": java.lang.Object&java.io.Serializable&java.util.function.@Test.TypeAnno Supplier<java.lang.String>",
"ac: java.lang.@Test.TypeAnno AutoCloseable",
"()->{ }: java.lang.@Test.TypeAnno AutoCloseable",
"e1: java.lang.@Test.TypeAnno Exception",
"e2: java.lang.@Test.TypeAnno Exception",
"e3: java.lang.@Test.TypeAnno IllegalStateException | java.lang.@Test.TypeAnno NullPointerException | java.lang.IllegalArgumentException",
"r_p: java.util.function.@Test.TypeAnno Supplier<java.lang.String>",
"r_p: java.util.function.@Test.TypeAnno Supplier<java.lang.String>"
);
actual.forEach(System.out::println);
if (!expected.equals(actual)) {
throw new AssertionError("Expected: " + expected + ", but got: " + actual);
}
}
static String typeToString(TypeMirror type) {
if (type != null && type.getKind() == TypeKind.UNION) {
return ((UnionType) type).getAlternatives().stream().map(t -> typeToString(t)).collect(Collectors.joining(" | "));
} else {
return String.valueOf(type);
}
}
static String treeToString(Tree tree) {
if (tree.toString().contains("\n")) {
System.err.println("!!!");
}
return String.valueOf(tree).replaceAll("\\R", " ");
}
}