From 58d2c1d47db8a7defe2c3319cfab943296cf34f1 Mon Sep 17 00:00:00 2001 From: Jan Lahoda Date: Wed, 4 Mar 2026 07:17:26 +0000 Subject: [PATCH] 8371155: Type annotations on local variables are classified after the local var initializer has been type checked Reviewed-by: vromero --- .../sun/tools/javac/code/TypeAnnotations.java | 94 +++++--- .../com/sun/tools/javac/comp/Attr.java | 5 +- .../TypeAnnotationsOnVariables.java | 211 ++++++++++++++++++ 3 files changed, 283 insertions(+), 27 deletions(-) create mode 100644 test/langtools/tools/javac/annotations/typeAnnotations/TypeAnnotationsOnVariables.java diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/TypeAnnotations.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/TypeAnnotations.java index 86319f20c73..8eba79c7480 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/TypeAnnotations.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/TypeAnnotations.java @@ -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 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 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 { diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java index 83b684e1225..444530c7266 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java @@ -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 { diff --git a/test/langtools/tools/javac/annotations/typeAnnotations/TypeAnnotationsOnVariables.java b/test/langtools/tools/javac/annotations/typeAnnotations/TypeAnnotationsOnVariables.java new file mode 100644 index 00000000000..2e68e18f8f7 --- /dev/null +++ b/test/langtools/tools/javac/annotations/typeAnnotations/TypeAnnotationsOnVariables.java @@ -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 r_f_i = () -> "r_f_i"; + static @TypeAnno Supplier r_f_s = () -> "r_f_s"; + + { + @TypeAnno Supplier r_init_i = () -> "r_init_i"; + } + + static { + @TypeAnno Supplier r_init_s = () -> "r_init_s"; + } + + void m() { + @TypeAnno Supplier r_m_i = () -> "r_m_i"; + } + + static void g() { + @TypeAnno Supplier r_g_s = () -> "r_g_s"; + } + + void h() { + t_cr(() -> "t_cr"); + } + + void i() { + t_no_cr((@TypeAnno Supplier)() -> "t_no_cr"); + } + + void j() { + t_no_cr((java.io.Serializable & @TypeAnno Supplier)() -> "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 r_p) { } + void t_no_cr(@TypeAnno Supplier r_p) { } + } + """); + Files.createDirectories(classes); + List 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() { + @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 expected = List.of( + "r_f_i: java.util.function.@Test.TypeAnno Supplier", + "()->\"r_f_i\": java.util.function.@Test.TypeAnno Supplier", + "r_f_s: java.util.function.@Test.TypeAnno Supplier", + "()->\"r_f_s\": java.util.function.@Test.TypeAnno Supplier", + "r_init_i: java.util.function.@Test.TypeAnno Supplier", + "()->\"r_init_i\": java.util.function.@Test.TypeAnno Supplier", + "r_init_s: java.util.function.@Test.TypeAnno Supplier", + "()->\"r_init_s\": java.util.function.@Test.TypeAnno Supplier", + "r_m_i: java.util.function.@Test.TypeAnno Supplier", + "()->\"r_m_i\": java.util.function.@Test.TypeAnno Supplier", + "r_g_s: java.util.function.@Test.TypeAnno Supplier", + "()->\"r_g_s\": java.util.function.@Test.TypeAnno Supplier", + "()->\"t_cr\": java.util.function.@Test.TypeAnno Supplier", + "()->\"t_no_cr\": java.util.function.@Test.TypeAnno Supplier", + "()->\"t_no_cr\": java.lang.Object&java.io.Serializable&java.util.function.@Test.TypeAnno Supplier", + "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", + "r_p: java.util.function.@Test.TypeAnno Supplier" + ); + + 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", " "); + } +}