mirror of
https://github.com/openjdk/jdk.git
synced 2026-03-09 23:50:22 +00:00
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:
parent
39b1e9d839
commit
58d2c1d47d
@ -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 {
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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", " ");
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user