From 666b94df62558e8dcaccc83c9407692745a4fa8b Mon Sep 17 00:00:00 2001 From: Dusan Balek Date: Wed, 13 May 2026 11:47:02 +0000 Subject: [PATCH] 8230518: printSource doesn't handle enums correctly Reviewed-by: abimpoudis, liach --- .../com/sun/tools/javac/tree/Pretty.java | 130 ++++++++-- .../tools/javac/enum/EnumSourceOutput.java | 157 ++++++++++++ .../tools/javac/patterns/PrettyTest.java | 7 +- .../javac/records/RecordSourceOutput.java | 234 ++++++++++++++++++ 4 files changed, 503 insertions(+), 25 deletions(-) create mode 100644 test/langtools/tools/javac/enum/EnumSourceOutput.java create mode 100644 test/langtools/tools/javac/records/RecordSourceOutput.java diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/Pretty.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/Pretty.java index c22dfd61637..f48bcaa8b7d 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/Pretty.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/Pretty.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 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 @@ -26,7 +26,7 @@ package com.sun.tools.javac.tree; import java.io.*; -import java.util.stream.Collectors; +import java.util.Optional; import com.sun.source.tree.MemberReferenceTree.ReferenceMode; import com.sun.source.tree.ModuleTree.ModuleKind; @@ -75,6 +75,10 @@ public class Pretty extends JCTree.Visitor { */ Name enclClassName; + /** The enclosing class flags. + */ + long enclClassFlags; + /** A table mapping trees to their documentation comments * (can be null) */ @@ -314,6 +318,22 @@ public class Pretty extends JCTree.Visitor { } } + /** Print record components. + */ + public void printRecordComponents(List stats) throws IOException { + print('('); + boolean first = true; + for (List l = stats; l.nonEmpty(); l = l.tail) { + if (isRecordComponent(l.head)) { + if (!first) { + print(", "); + } + printStat(l.head); + first = false; + } + } + print(')'); + } /** Print a block. */ public void printBlock(List stats) throws IOException { @@ -347,7 +367,25 @@ public class Pretty extends JCTree.Visitor { print(';'); println(); for (List l = stats; l.nonEmpty(); l = l.tail) { - if (!isEnumerator(l.head)) { + if (!isEnumerator(l.head) && (!sourceOutput || !isGeneratedDefaultConstructor(l.head))) { + align(); + printStat(l.head); + println(); + } + } + undent(); + align(); + print('}'); + } + + /** Print a block. + */ + public void printRecordBody(List stats) throws IOException { + print('{'); + println(); + indent(); + for (List l = stats; l.nonEmpty(); l = l.tail) { + if (!isRecordComponent(l.head) && (!sourceOutput || !isGeneratedDefaultConstructor(l.head))) { align(); printStat(l.head); println(); @@ -363,6 +401,21 @@ public class Pretty extends JCTree.Visitor { return t.hasTag(VARDEF) && (((JCVariableDecl) t).mods.flags & ENUM) != 0; } + /** Is the given tree a record component? */ + boolean isRecordComponent(JCTree t) { + return t.hasTag(VARDEF) && (((JCVariableDecl) t).mods.flags & RECORD) != 0; + } + + /** Is the given tree a compact record constructor? */ + boolean isCompactRecordConstructor(JCTree t) { + return t.hasTag(METHODDEF) && (((JCMethodDecl) t).mods.flags & COMPACT_RECORD_CONSTRUCTOR) != 0; + } + + /** Is the given tree a generated default constructor? */ + boolean isGeneratedDefaultConstructor(JCTree t) { + return t.hasTag(METHODDEF) && (((JCMethodDecl) t).mods.flags & GENERATEDCONSTR) != 0; + } + /** Print unit consisting of package clause and import statements in toplevel, * followed by class definition. if class definition == null, * print all definitions in toplevel. @@ -566,6 +619,8 @@ public class Pretty extends JCTree.Visitor { printFlags(tree.mods.flags & ~INTERFACE); Name enclClassNamePrev = enclClassName; enclClassName = tree.name; + long enclClassFlagsPrev = enclClassFlags; + enclClassFlags = tree.mods.flags; if ((tree.mods.flags & INTERFACE) != 0) { print("interface "); print(tree.name); @@ -581,10 +636,15 @@ public class Pretty extends JCTree.Visitor { } else { if ((tree.mods.flags & ENUM) != 0) print("enum "); + else if ((tree.mods.flags & RECORD) != 0) + print("record "); else print("class "); print(tree.name); printTypeParameters(tree.typarams); + if ((tree.mods.flags & RECORD) != 0) { + printRecordComponents(tree.defs); + } if (tree.extending != null) { print(" extends "); printExpr(tree.extending); @@ -601,10 +661,13 @@ public class Pretty extends JCTree.Visitor { print(' '); if ((tree.mods.flags & ENUM) != 0) { printEnumBody(tree.defs); + } else if ((tree.mods.flags & RECORD) != 0) { + printRecordBody(tree.defs); } else { printBlock(tree.defs); } enclClassName = enclClassNamePrev; + enclClassFlags = enclClassFlagsPrev; } catch (IOException e) { throw new UncheckedIOException(e); } @@ -627,26 +690,41 @@ public class Pretty extends JCTree.Visitor { print(' '); print(tree.name); } - print('('); - if (tree.recvparam!=null) { - printExpr(tree.recvparam); - if (tree.params.size() > 0) { - print(", "); + if (!isCompactRecordConstructor(tree)) { + print('('); + if (tree.recvparam!=null) { + printExpr(tree.recvparam); + if (tree.params.size() > 0) { + print(", "); + } + } + printExprs(tree.params); + print(')'); + if (tree.thrown.nonEmpty()) { + print(" throws "); + printExprs(tree.thrown); + } + if (tree.defaultValue != null) { + print(" default "); + printExpr(tree.defaultValue); } - } - printExprs(tree.params); - print(')'); - if (tree.thrown.nonEmpty()) { - print(" throws "); - printExprs(tree.thrown); - } - if (tree.defaultValue != null) { - print(" default "); - printExpr(tree.defaultValue); } if (tree.body != null) { print(' '); - printStat(tree.body); + if (tree.name == tree.name.table.names.init && + ((enclClassFlags & ENUM) != 0 || + (enclClassFlags & RECORD) != 0)) { + ListBuffer buf = new ListBuffer<>(); + for (List l = tree.body.stats; l.nonEmpty(); l = l.tail) { + // Filter out super constructor calls + if (!TreeInfo.isSuperCall(l.head)) { + buf.append(l.head); + } + } + printBlock(buf.toList()); + } else { + printStat(tree.body); + } } else { print(';'); } @@ -662,6 +740,7 @@ public class Pretty extends JCTree.Visitor { } printDocComment(tree); if ((tree.mods.flags & ENUM) != 0) { + printAnnotations(tree.mods.annotations); print("/*public static final*/ "); print(tree.name); if (tree.init != null) { @@ -676,7 +755,13 @@ public class Pretty extends JCTree.Visitor { } if (init.def != null && init.def.defs != null) { print(' '); - printBlock(init.def.defs); + ListBuffer buf = new ListBuffer<>(); + for (List l = init.def.defs; l.nonEmpty(); l = l.tail) { + if (!isGeneratedDefaultConstructor(l.head)) { + buf.append(l.head); + } + } + printBlock(buf.toList()); } return; }else { @@ -707,6 +792,11 @@ public class Pretty extends JCTree.Visitor { printExpr(tree.init); print(" */"); } + } else if ((tree.mods.flags & RECORD) != 0) { + printTypeAnnotations(tree.mods.annotations); + printExpr(tree.vartype); + print(' '); + print(tree.name); } else { printExpr(tree.mods); if ((tree.mods.flags & VARARGS) != 0) { diff --git a/test/langtools/tools/javac/enum/EnumSourceOutput.java b/test/langtools/tools/javac/enum/EnumSourceOutput.java new file mode 100644 index 00000000000..4d000e42b29 --- /dev/null +++ b/test/langtools/tools/javac/enum/EnumSourceOutput.java @@ -0,0 +1,157 @@ +/* + * 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 8230518 + * @summary Test that source output is correct for enums. + * @library /tools/lib + * @modules + * jdk.compiler/com.sun.tools.javac.api + * jdk.compiler/com.sun.tools.javac.main + * @build toolbox.ToolBox toolbox.JavacTask + * @run junit ${test.main.class} + */ + +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import toolbox.JavacTask; +import toolbox.ToolBox; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInfo; + +public class EnumSourceOutput { + + Path base; + ToolBox tb = new ToolBox(); + + @Test + void testAnnotations() throws Exception { + Path classes = base.resolve("classes"); + Files.createDirectories(classes); + new JavacTask(tb) + .options("-d", classes.toString(), "-printsource") + .sources(""" + @Deprecated + public enum E { + @Deprecated + ONE + } + """) + .run() + .writeAll(); + Path printed = classes.resolve("E.java"); + String printedContent = Files.readString(printed); + Assertions.assertEquals(""" + @Deprecated + public enum E { + @Deprecated + /*public static final*/ ONE /*enum*/ ; + } + """.replaceAll("\\s+", " ").trim(), + printedContent.replaceAll("\\s+", " ").trim()); + new JavacTask(tb) + .options("-d", classes.toString()) + .files(printed) + .run() + .writeAll(); + } + + @Test + void testGeneratedConstructors() throws Exception { + Path classes = base.resolve("classes"); + Files.createDirectories(classes); + new JavacTask(tb) + .options("-d", classes.toString(), "-printsource") + .sources(""" + public enum E { + ONE { + void f() {} + }; + abstract void f(); + } + """) + .run() + .writeAll(); + Path printed = classes.resolve("E.java"); + String printedContent = Files.readString(printed); + Assertions.assertEquals(""" + public enum E { + /*public static final*/ ONE /*enum*/ { + void f() { + } + }; + abstract void f(); + } + """.replaceAll("\\s+", " ").trim(), + printedContent.replaceAll("\\s+", " ").trim()); + new JavacTask(tb) + .options("-d", classes.toString()) + .files(printed) + .run() + .writeAll(); + } + + @Test + void testExplicitConstructors() throws Exception { + Path classes = base.resolve("classes"); + Files.createDirectories(classes); + new JavacTask(tb) + .options("-d", classes.toString(), "-printsource") + .sources(""" + public enum E { + ONE(1); + E(int i) {} + } + """) + .run() + .writeAll(); + Path printed = classes.resolve("E.java"); + String printedContent = Files.readString(printed); + Assertions.assertEquals(""" + public enum E { + /*public static final*/ ONE /*enum*/ (1); + E(int i) { + } + } + """.replaceAll("\\s+", " ").trim(), + printedContent.replaceAll("\\s+", " ").trim()); + new JavacTask(tb) + .options("-d", classes.toString()) + .files(printed) + .run() + .writeAll(); + } + + @BeforeEach + public void setUp(TestInfo info) { + base = Paths.get(".") + .resolve(info.getTestMethod() + .orElseThrow() + .getName()); + } +} diff --git a/test/langtools/tools/javac/patterns/PrettyTest.java b/test/langtools/tools/javac/patterns/PrettyTest.java index 420d3a16020..39c14e2b06a 100644 --- a/test/langtools/tools/javac/patterns/PrettyTest.java +++ b/test/langtools/tools/javac/patterns/PrettyTest.java @@ -80,13 +80,10 @@ public class PrettyTest { b = o instanceof R2(R(_), var t); } \n\ - class R { - private final String s; + record R(String s) { } \n\ - class R2 { - private final R r; - private final String s; + record R2(R r, String s) { } }"""; if (!expected.equals(pretty)) { diff --git a/test/langtools/tools/javac/records/RecordSourceOutput.java b/test/langtools/tools/javac/records/RecordSourceOutput.java new file mode 100644 index 00000000000..5c16e6be326 --- /dev/null +++ b/test/langtools/tools/javac/records/RecordSourceOutput.java @@ -0,0 +1,234 @@ +/* + * 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 8230518 + * @summary Test that source output is correct for records. + * @library /tools/lib + * @modules + * jdk.compiler/com.sun.tools.javac.api + * jdk.compiler/com.sun.tools.javac.main + * @build toolbox.ToolBox toolbox.JavacTask + * @run junit ${test.main.class} + */ + +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import toolbox.JavacTask; +import toolbox.ToolBox; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInfo; + +public class RecordSourceOutput { + + Path base; + ToolBox tb = new ToolBox(); + + @Test + void testSimpleRecord() throws Exception { + Path classes = base.resolve("classes"); + Files.createDirectories(classes); + new JavacTask(tb) + .options("-d", classes.toString(), "-printsource") + .sources(""" + public record R(int x, int y) {} + """) + .run() + .writeAll(); + Path printed = classes.resolve("R.java"); + String printedContent = Files.readString(printed); + Assertions.assertEquals(""" + public record R(int x, int y) { + } + """.replaceAll("\\s+", " ").trim(), + printedContent.replaceAll("\\s+", " ").trim()); + new JavacTask(tb) + .options("-d", classes.toString()) + .files(printed) + .run() + .writeAll(); + } + + @Test + void testAnnotations() throws Exception { + Path classes = base.resolve("classes"); + Files.createDirectories(classes); + new JavacTask(tb) + .options("-d", classes.toString(), "-printsource") + .sources(""" + @Deprecated + public record R(@Deprecated int x) {} + """) + .run() + .writeAll(); + Path printed = classes.resolve("R.java"); + String printedContent = Files.readString(printed); + Assertions.assertEquals(""" + @Deprecated + public record R(@Deprecated int x) { + } + """.replaceAll("\\s+", " ").trim(), + printedContent.replaceAll("\\s+", " ").trim()); + new JavacTask(tb) + .options("-d", classes.toString()) + .files(printed) + .run() + .writeAll(); + } + + @Test + void testVarArg() throws Exception { + Path classes = base.resolve("classes"); + Files.createDirectories(classes); + new JavacTask(tb) + .options("-d", classes.toString(), "-printsource") + .sources(""" + public record R(int... arr) {} + """) + .run() + .writeAll(); + Path printed = classes.resolve("R.java"); + String printedContent = Files.readString(printed); + Assertions.assertEquals(""" + public record R(int[] arr) { + } + """.replaceAll("\\s+", " ").trim(), + printedContent.replaceAll("\\s+", " ").trim()); + new JavacTask(tb) + .options("-d", classes.toString()) + .files(printed) + .run() + .writeAll(); + } + + @Test + void testCompactConstructor() throws Exception { + Path classes = base.resolve("classes"); + Files.createDirectories(classes); + new JavacTask(tb) + .options("-d", classes.toString(), "-printsource") + .sources(""" + public record R(int x, int y) { + public R { + x += 1; + y += 1; + } + } + """) + .run() + .writeAll(); + Path printed = classes.resolve("R.java"); + String printedContent = Files.readString(printed); + Assertions.assertEquals(""" + public record R(int x, int y) { + public R { + x += 1; + y += 1; + } + } + """.replaceAll("\\s+", " ").trim(), + printedContent.replaceAll("\\s+", " ").trim()); + new JavacTask(tb) + .options("-d", classes.toString()) + .files(printed) + .run() + .writeAll(); + } + + @Test + void testExplicitConstructor() throws Exception { + Path classes = base.resolve("classes"); + Files.createDirectories(classes); + new JavacTask(tb) + .options("-d", classes.toString(), "-printsource") + .sources(""" + public record R(int x) { + public R(int x) { + this.x = x; + } + } + """) + .run() + .writeAll(); + Path printed = classes.resolve("R.java"); + String printedContent = Files.readString(printed); + Assertions.assertEquals(""" + public record R(int x) { + public R(int x) { + this.x = x; + } + } + """.replaceAll("\\s+", " ").trim(), + printedContent.replaceAll("\\s+", " ").trim()); + new JavacTask(tb) + .options("-d", classes.toString()) + .files(printed) + .run() + .writeAll(); + } + + @Test + void testExplicitAccessor() throws Exception { + Path classes = base.resolve("classes"); + Files.createDirectories(classes); + new JavacTask(tb) + .options("-d", classes.toString(), "-printsource") + .sources(""" + public record R(int x) { + public int x() { + return x + 1; + } + } + """) + .run() + .writeAll(); + Path printed = classes.resolve("R.java"); + String printedContent = Files.readString(printed); + Assertions.assertEquals(""" + public record R(int x) { + public int x() { + return x + 1; + } + } + """.replaceAll("\\s+", " ").trim(), + printedContent.replaceAll("\\s+", " ").trim()); + new JavacTask(tb) + .options("-d", classes.toString()) + .files(printed) + .run() + .writeAll(); + } + + @BeforeEach + public void setUp(TestInfo info) { + base = Paths.get(".") + .resolve(info.getTestMethod() + .orElseThrow() + .getName()); + } +}