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 caa081505b6..e49ffa92128 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 @@ -1233,27 +1233,9 @@ public class TypeAnnotations { try { currentLambda = tree; - int i = 0; - for (JCVariableDecl param : tree.params) { - if (!param.mods.annotations.isEmpty()) { - // Nothing to do for separateAnnotationsKinds if - // there are no annotations of either kind. - final TypeAnnotationPosition pos = TypeAnnotationPosition - .methodParameter(tree, i, param.vartype.pos); - push(param); - try { - if (!param.declaredUsingVar()) { - separateAnnotationsKinds(param, param.vartype, param.sym.type, param.sym, pos); - } - } finally { - pop(); - } - } - ++i; - } - scan(tree.body); - scan(tree.params); + + //parameters are handled separately as variables } finally { currentLambda = prevLambda; } @@ -1278,8 +1260,12 @@ public class TypeAnnotations { TypeAnnotationPosition.exceptionParameter(currentLambda, tree.pos); separateAnnotationsKinds(tree, tree.vartype, tree.sym.type, tree.sym, pos); + } else if (currentLambda != null && !tree.declaredUsingVar() && currentLambda.params.contains(tree)) { + final TypeAnnotationPosition pos = TypeAnnotationPosition + .methodParameter(currentLambda, currentLambda.params.indexOf(tree), tree.vartype.pos); + separateAnnotationsKinds(tree, tree.vartype, tree.sym.type, tree.sym, pos); } else { - // (real) parameters are handled in visitMethodDef or visitLambda. + // method parameters are handled in visitMethodDef. } } } else if (tree.sym.getKind() == ElementKind.FIELD) { diff --git a/test/langtools/tools/javac/annotations/typeAnnotations/TypeAnnotationsOnVariables.java b/test/langtools/tools/javac/annotations/typeAnnotations/TypeAnnotationsOnVariables.java index e5a1e5650d3..f89e3ff7398 100644 --- a/test/langtools/tools/javac/annotations/typeAnnotations/TypeAnnotationsOnVariables.java +++ b/test/langtools/tools/javac/annotations/typeAnnotations/TypeAnnotationsOnVariables.java @@ -23,7 +23,7 @@ /* * @test - * @bug 8371155 8379550 + * @bug 8371155 8379550 8384843 * @summary Verify type annotations on local-like variables are propagated to * their types at an appropriate time. * @library /tools/lib @@ -56,7 +56,9 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.Optional; +import java.util.function.Function; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -299,7 +301,7 @@ public class TypeAnnotationsOnVariables { Path testClass = classes.resolve("Test.class"); TestClassDesc testClassDesc = TestClassDesc.create(testClass); MethodModel oMethod = singletonValue(testClassDesc.name2Method().get("o")); - var oTypeAnnos = getAnnotations(oMethod); + var oTypeAnnos = getAnnotationsFromCode(oMethod); assertFalse(oTypeAnnos.isPresent(), () -> oTypeAnnos.toString()); checkTypeAnnotations(testClassDesc, @@ -337,11 +339,238 @@ public class TypeAnnotationsOnVariables { " Test$TypeAnno"); } + @Test + void explicitLambdaHeader1() 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.BiConsumer; + import java.util.function.Consumer; + import java.util.List; + + class Test { + @Target(ElementType.TYPE_USE) + @interface TypeAnno { } + @Target(ElementType.TYPE_USE) + @interface TypeAnno2 { } + + static final Consumer> TEST1 = + (List<@TypeAnno String> arg1) -> {}; + static final BiConsumer, List<@TypeAnno2 String>> TEST2 = + (List<@TypeAnno String> arg11, List<@TypeAnno2 String> arg12) -> {}; + + private void test() { + Consumer> test1 = + (List<@TypeAnno String> arg2) -> {}; + BiConsumer, List<@TypeAnno2 String>> test2 = + (List<@TypeAnno String> arg21, List<@TypeAnno2 String> arg22) -> {}; + } + } + """); + 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( + "TEST1: java.util.function.Consumer>", + "(List<@TypeAnno String> arg1)->{ }: java.util.function.Consumer>", + "arg1: java.util.List", + "TEST2: java.util.function.BiConsumer,java.util.List>", + "(List<@TypeAnno String> arg11, List<@TypeAnno2 String> arg12)->{ }: java.util.function.BiConsumer,java.util.List>", + "arg11: java.util.List", + "arg12: java.util.List", + "test1: java.util.function.Consumer>", + "(List<@TypeAnno String> arg2)->{ }: java.util.function.Consumer>", + "arg2: java.util.List", + "test2: java.util.function.BiConsumer,java.util.List>", + "(List<@TypeAnno String> arg21, List<@TypeAnno2 String> arg22)->{ }: java.util.function.BiConsumer,java.util.List>", + "arg21: java.util.List", + "arg22: java.util.List" + ); + + actual.forEach(System.out::println); + if (!expected.equals(actual)) { + throw new AssertionError("Expected: " + expected + ", but got: " + actual); + } + + Path testClass = classes.resolve("Test.class"); + TestClassDesc testClassDesc = TestClassDesc.create(testClass); + MethodModel clInit = singletonValue(testClassDesc.name2Method().get("")); + assertEmpty(getAnnotationsFromHeader(clInit)); + assertEmpty(getAnnotationsFromCode(clInit)); + + checkTypeAnnotations(testClassDesc, + "test", + this::getAnnotationsFromCode, + " 0: LTest$TypeAnno;(): LOCAL_VARIABLE, {start_pc=6, length=7, index=1}, location=[TYPE_ARGUMENT(0), TYPE_ARGUMENT(0)]", + " Test$TypeAnno", + " 1: LTest$TypeAnno;(): LOCAL_VARIABLE, {start_pc=12, length=1, index=2}, location=[TYPE_ARGUMENT(0), TYPE_ARGUMENT(0)]", + " Test$TypeAnno", + " 2: LTest$TypeAnno2;(): LOCAL_VARIABLE, {start_pc=12, length=1, index=2}, location=[TYPE_ARGUMENT(1), TYPE_ARGUMENT(0)]", + " Test$TypeAnno2"); + + checkTypeAnnotations(testClassDesc, + "lambda$static$0", + this::getAnnotationsFromHeader, + " 0: LTest$TypeAnno;(): METHOD_FORMAL_PARAMETER, param_index=0, location=[TYPE_ARGUMENT(0)]", + " Test$TypeAnno"); + + checkTypeAnnotations(testClassDesc, + "lambda$static$1", + this::getAnnotationsFromHeader, + " 0: LTest$TypeAnno;(): METHOD_FORMAL_PARAMETER, param_index=0, location=[TYPE_ARGUMENT(0)]", + " Test$TypeAnno", + " 1: LTest$TypeAnno2;(): METHOD_FORMAL_PARAMETER, param_index=1, location=[TYPE_ARGUMENT(0)]", + " Test$TypeAnno2"); + + checkTypeAnnotations(testClassDesc, + "lambda$test$0", + this::getAnnotationsFromHeader, + " 0: LTest$TypeAnno;(): METHOD_FORMAL_PARAMETER, param_index=0, location=[TYPE_ARGUMENT(0)]", + " Test$TypeAnno"); + + checkTypeAnnotations(testClassDesc, + "lambda$test$1", + this::getAnnotationsFromHeader, + " 0: LTest$TypeAnno;(): METHOD_FORMAL_PARAMETER, param_index=0, location=[TYPE_ARGUMENT(0)]", + " Test$TypeAnno", + " 1: LTest$TypeAnno2;(): METHOD_FORMAL_PARAMETER, param_index=1, location=[TYPE_ARGUMENT(0)]", + " Test$TypeAnno2"); + } + + @Test + void explicitLambdaHeader2() 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.Consumer; + import java.util.List; + + class Test { + @Target(ElementType.TYPE_USE) + @interface TypeAnno { } + + static final Consumer> TEST = + (@TypeAnno List arg) -> {}; + + private void test() { + Consumer> test = + (@TypeAnno List arg) -> {}; + } + } + """); + 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( + "TEST: java.util.function.Consumer>", + "(@TypeAnno List arg)->{ }: java.util.function.Consumer>", + "arg: java.util.@Test.TypeAnno List", + "test: java.util.function.Consumer>", + "(@TypeAnno List arg)->{ }: java.util.function.Consumer>", + "arg: java.util.@Test.TypeAnno List" + ); + + actual.forEach(System.out::println); + if (!expected.equals(actual)) { + throw new AssertionError("Expected: " + expected + ", but got: " + actual); + } + + Path testClass = classes.resolve("Test.class"); + TestClassDesc testClassDesc = TestClassDesc.create(testClass); + MethodModel clInit = singletonValue(testClassDesc.name2Method().get("")); + assertEmpty(getAnnotationsFromHeader(clInit)); + assertEmpty(getAnnotationsFromCode(clInit)); + MethodModel test = singletonValue(testClassDesc.name2Method().get("test")); + assertEmpty(getAnnotationsFromHeader(test)); + assertEmpty(getAnnotationsFromCode(test)); + + checkTypeAnnotations(testClassDesc, + "lambda$static$0", + this::getAnnotationsFromHeader, + " 0: LTest$TypeAnno;(): METHOD_FORMAL_PARAMETER, param_index=0", + " Test$TypeAnno"); + + checkTypeAnnotations(testClassDesc, + "lambda$test$0", + this::getAnnotationsFromHeader, + " 0: LTest$TypeAnno;(): METHOD_FORMAL_PARAMETER, param_index=0", + " Test$TypeAnno"); + } + private void checkTypeAnnotations(TestClassDesc testClassDesc, String lambdaMethodName, String... expectedEntries) throws IOException { + checkTypeAnnotations(testClassDesc, lambdaMethodName, this::getAnnotationsFromCode, expectedEntries); + } + + private void checkTypeAnnotations(TestClassDesc testClassDesc, + String lambdaMethodName, + Function> annotationsGetter, + String... expectedEntries) throws IOException { MethodModel lambdaMethod = singletonValue(testClassDesc.name2Method().get(lambdaMethodName)); - var lambdaTypeAnnos = getAnnotations(lambdaMethod); + var lambdaTypeAnnos = annotationsGetter.apply(lambdaMethod); if (expectedEntries.length == 0) { assertFalse(lambdaTypeAnnos.isPresent(), () -> lambdaTypeAnnos.toString()); } else { @@ -351,6 +580,7 @@ public class TypeAnnotationsOnVariables { () -> lambdaTypeAnnos.orElseThrow().annotations().toString()); checkJavapOutput(testClassDesc, + lambdaMethodName, List.of(expectedEntries)); } } @@ -360,13 +590,17 @@ public class TypeAnnotationsOnVariables { return values.get(0); } - private Optional getAnnotations(MethodModel m) { + private Optional getAnnotationsFromCode(MethodModel m) { return m.findAttribute(Attributes.code()) .orElseThrow() .findAttribute(Attributes.runtimeInvisibleTypeAnnotations()); } - void checkJavapOutput(TestClassDesc testClassDesc, List expectedOutput) throws IOException { + private Optional getAnnotationsFromHeader(MethodModel m) { + return m.findAttribute(Attributes.runtimeInvisibleTypeAnnotations()); + } + + void checkJavapOutput(TestClassDesc testClassDesc, String nameOfMethodToCheck, List expectedOutput) throws IOException { String javapOut = new JavapTask(tb) .options("-v", "-p") .classes(testClassDesc.pathToClass().toString()) @@ -385,15 +619,42 @@ public class TypeAnnotationsOnVariables { m.appendTail(expandedJavapOutBuilder); String expandedJavapOut = expandedJavapOutBuilder.toString(); + boolean inClass = false; + String currentFeature = null; + Map> feature2Text = new HashMap<>(); + + for (String line : expandedJavapOut.split("\\R")) { + if (line.equals("{")) { + inClass = true; + } else if (line.equals("}")) { + inClass = false; + currentFeature = null; + } else if (inClass && line.startsWith(" ") && line.charAt(2) != ' ') { + currentFeature = line; + } else if (currentFeature != null) { + feature2Text.computeIfAbsent(currentFeature, _ -> new ArrayList<>()) + .add(line); + } + } + + List> methodContents = feature2Text.entrySet().stream().filter(e -> e.getKey().contains(" " + nameOfMethodToCheck + "(")).map(Entry::getValue).toList(); + + assertEquals(1, methodContents.size(), methodContents.toString()); + + String linearMethodContents = methodContents.get(0).stream().collect(Collectors.joining("\n")); for (String expected : expectedOutput) { - if (!expandedJavapOut.contains(expected)) { - System.err.println(expandedJavapOut); + if (!linearMethodContents.contains(expected)) { + System.err.println(linearMethodContents); throw new AssertionError("unexpected output"); } } } + private void assertEmpty(Optional value) { + assertFalse(value.isPresent(), () -> value.toString()); + } + record TestClassDesc(Path pathToClass, Map> name2Method, Map cpIndex2Name) {