8230518: printSource doesn't handle enums correctly

Reviewed-by: abimpoudis, liach
This commit is contained in:
Dusan Balek 2026-05-13 11:47:02 +00:00 committed by Aggelos Biboudis
parent d7e0517469
commit 666b94df62
4 changed files with 503 additions and 25 deletions

View File

@ -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<JCTree> stats) throws IOException {
print('(');
boolean first = true;
for (List<JCTree> 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<? extends JCTree> stats) throws IOException {
@ -347,7 +367,25 @@ public class Pretty extends JCTree.Visitor {
print(';');
println();
for (List<JCTree> 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<JCTree> stats) throws IOException {
print('{');
println();
indent();
for (List<JCTree> 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<JCStatement> buf = new ListBuffer<>();
for (List<JCStatement> 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<JCTree> buf = new ListBuffer<>();
for (List<JCTree> 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) {

View File

@ -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());
}
}

View File

@ -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)) {

View File

@ -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());
}
}