diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/JavacParser.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/JavacParser.java index 27cb44bebba..a93f5a9a794 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/JavacParser.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/JavacParser.java @@ -457,9 +457,13 @@ public class JavacParser implements Parser { } protected JCErroneous syntaxError(int pos, List errs, Error errorKey) { + return syntaxError(pos, errs, errorKey, false); + } + + private JCErroneous syntaxError(int pos, List errs, Error errorKey, boolean noEofError) { setErrorEndPos(pos); JCErroneous err = F.at(pos).Erroneous(errs); - reportSyntaxError(err, errorKey); + reportSyntaxError(err, errorKey, noEofError); if (errs != null) { JCTree last = errs.last(); if (last != null) @@ -486,9 +490,13 @@ public class JavacParser implements Parser { * arguments, unless one was already reported at the same position. */ protected void reportSyntaxError(JCDiagnostic.DiagnosticPosition diagPos, Error errorKey) { + reportSyntaxError(diagPos, errorKey, false); + } + + private void reportSyntaxError(JCDiagnostic.DiagnosticPosition diagPos, Error errorKey, boolean noEofError) { int pos = diagPos.getPreferredPosition(); if (pos > S.errPos() || pos == Position.NOPOS) { - if (token.kind == EOF) { + if (token.kind == EOF && !noEofError) { log.error(DiagnosticFlag.SYNTAX, diagPos, Errors.PrematureEof); } else { log.error(DiagnosticFlag.SYNTAX, diagPos, errorKey); @@ -4093,6 +4101,13 @@ public class JavacParser implements Parser { checkSourceLevel(token.pos, Feature.IMPLICIT_CLASSES); defs.appendList(topLevelMethodOrFieldDeclaration(mods, docComment)); isImplicitClass = true; + } else if (isDefiniteStatementStartToken()) { + int startPos = token.pos; + List statements = blockStatement(); + defs.append(syntaxError(startPos, + statements, + Errors.StatementNotExpected, + true)); } else { JCTree def = typeDeclaration(mods, docComment); if (def instanceof JCExpressionStatement statement) @@ -4325,6 +4340,9 @@ public class JavacParser implements Parser { JCDiagnostic.Error error; if (parseModuleInfo) { error = Errors.ExpectedModuleOrOpen; + } else if (Feature.IMPLICIT_CLASSES.allowedInSource(source) && + (!preview.isPreview(Feature.IMPLICIT_CLASSES) || preview.isEnabled())) { + error = Errors.ClassMethodOrFieldExpected; } else if (allowRecords) { error = Errors.Expected4(CLASS, INTERFACE, ENUM, "record"); } else { @@ -4907,6 +4925,12 @@ public class JavacParser implements Parser { return defs; } + } else if (token.kind == LPAREN && type.hasTag(IDENT)) { + log.error(DiagnosticFlag.SYNTAX, pos, Errors.InvalidMethDeclRetTypeReq); + + return List.of(methodDeclaratorRest( + pos, mods, null, names.init, typarams, + false, true, false, dc)); } return List.of(F.Erroneous()); diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/compiler.properties b/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/compiler.properties index 6fd59438220..7e41f43aeb2 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/compiler.properties +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/compiler.properties @@ -1619,6 +1619,9 @@ compiler.err.no.java.lang=\ compiler.err.statement.not.expected=\ statements not expected outside of methods and initializers +compiler.err.class.method.or.field.expected=\ + class, interface, annotation type, enum, record, method or field expected + ##### # Fatal Errors diff --git a/test/langtools/tools/javac/ImplicitClass/ErrorRecovery.java b/test/langtools/tools/javac/ImplicitClass/ErrorRecovery.java new file mode 100644 index 00000000000..883580893af --- /dev/null +++ b/test/langtools/tools/javac/ImplicitClass/ErrorRecovery.java @@ -0,0 +1,222 @@ +/* + * Copyright (c) 2024, 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 8338301 + * @summary Verify error recovery and reporting related to implicitly declared classes + * @library /tools/lib + * @modules jdk.compiler/com.sun.tools.javac.api + * jdk.compiler/com.sun.tools.javac.main + * jdk.compiler/com.sun.tools.javac.util + * @build toolbox.ToolBox toolbox.JavacTask + * @run main ErrorRecovery +*/ + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.Objects; + +import toolbox.TestRunner; +import toolbox.JavacTask; +import toolbox.JavaTask; +import toolbox.Task; +import toolbox.Task.OutputKind; +import toolbox.ToolBox; + +public class ErrorRecovery extends TestRunner { + + private static final String SOURCE_VERSION = System.getProperty("java.specification.version"); + private ToolBox tb; + + public static void main(String... args) throws Exception { + new ErrorRecovery().runTests(); + } + + ErrorRecovery() { + super(System.err); + tb = new ToolBox(); + } + + public void runTests() throws Exception { + runTests(m -> new Object[] { Paths.get(m.getName()) }); + } + + @Test + public void testMethodNoReturnType(Path base) throws Exception { + Path current = base.resolve("."); + Path src = current.resolve("src"); + Path classes = current.resolve("classes"); + tb.writeFile(src.resolve("Test.java"), + """ + main() {} + """); + + Files.createDirectories(classes); + + List log = new JavacTask(tb) + .options("-XDrawDiagnostics", + "--enable-preview", "--release", SOURCE_VERSION) + .outdir(classes) + .files(tb.findJavaFiles(src)) + .run(Task.Expect.FAIL) + .writeAll() + .getOutputLines(OutputKind.DIRECT); + List expected = List.of( + "Test.java:1:1: compiler.err.invalid.meth.decl.ret.type.req", + "- compiler.note.preview.filename: Test.java, DEFAULT", + "- compiler.note.preview.recompile", + "1 error" + ); + if (!Objects.equals(expected, log)) { + throw new AssertionError("Unexpected output: " + log + + ", while expecting: " + expected); + } + } + + @Test + public void testStatement(Path base) throws Exception { + Path current = base.resolve("."); + Path src = current.resolve("src"); + Path classes = current.resolve("classes"); + tb.writeFile(src.resolve("Test.java"), + """ + if (true) {int var = 0;} + """); + + Files.createDirectories(classes); + + List log = new JavacTask(tb) + .options("-XDrawDiagnostics", + "-XDshould-stop.at=FLOW", + "--enable-preview", "--release", SOURCE_VERSION) + .outdir(classes) + .files(tb.findJavaFiles(src)) + .run(Task.Expect.FAIL) + .writeAll() + .getOutputLines(OutputKind.DIRECT); + List expected = List.of( + "Test.java:1:1: compiler.err.statement.not.expected", + "1 error" + ); + if (!Objects.equals(expected, log)) { + throw new AssertionError("Unexpected output: " + log + + ", while expecting: " + expected); + } + } + + @Test + public void testExtraSemi(Path base) throws Exception { + Path current = base.resolve("."); + Path src = current.resolve("src"); + Path classes = current.resolve("classes"); + tb.writeFile(src.resolve("Test.java"), + """ + class C {}; + void main() {}; + """); + + Files.createDirectories(classes); + + new JavacTask(tb) + .options("-XDrawDiagnostics", + "--enable-preview", "--release", SOURCE_VERSION) + .outdir(classes) + .files(tb.findJavaFiles(src)) + .run(Task.Expect.SUCCESS) + .writeAll(); + } + + @Test + public void testVeryBroken(Path base) throws Exception { + Path current = base.resolve("."); + Path src = current.resolve("src"); + Path classes = current.resolve("classes"); + tb.writeFile(src.resolve("Test.java"), + """ + "neither-of-class-method-file-statement" + """); + + Files.createDirectories(classes); + + List log; + List expected; + + log = new JavacTask(tb) + .options("-XDrawDiagnostics", + "--enable-preview", "--release", SOURCE_VERSION) + .outdir(classes) + .files(tb.findJavaFiles(src)) + .run(Task.Expect.FAIL) + .writeAll() + .getOutputLines(OutputKind.DIRECT); + expected = List.of( + "Test.java:1:1: compiler.err.class.method.or.field.expected", + "1 error" + ); + + if (!Objects.equals(expected, log)) { + throw new AssertionError("Unexpected output: " + log + + ", while expecting: " + expected); + } + + log = new JavacTask(tb) + .options("-XDrawDiagnostics") + .outdir(classes) + .files(tb.findJavaFiles(src)) + .run(Task.Expect.FAIL) + .writeAll() + .getOutputLines(OutputKind.DIRECT); + expected = List.of( + "Test.java:1:1: compiler.err.expected4: class, interface, enum, record", + "1 error" + ); + + if (!Objects.equals(expected, log)) { + throw new AssertionError("Unexpected output: " + log + + ", while expecting: " + expected); + } + + log = new JavacTask(tb) + .options("-XDrawDiagnostics", + "--release", "17") + .outdir(classes) + .files(tb.findJavaFiles(src)) + .run(Task.Expect.FAIL) + .writeAll() + .getOutputLines(OutputKind.DIRECT); + expected = List.of( + "Test.java:1:1: compiler.err.expected4: class, interface, enum, record", + "1 error" + ); + + if (!Objects.equals(expected, log)) { + throw new AssertionError("Unexpected output: " + log + + ", while expecting: " + expected); + } + } + +} diff --git a/test/langtools/tools/javac/diags/examples/ClassMethodOrFieldExpected.java b/test/langtools/tools/javac/diags/examples/ClassMethodOrFieldExpected.java new file mode 100644 index 00000000000..a2431a9b61c --- /dev/null +++ b/test/langtools/tools/javac/diags/examples/ClassMethodOrFieldExpected.java @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2024, 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. + */ + +// key: compiler.err.class.method.or.field.expected +// options: --enable-preview --source ${jdk.version} + +"neither-of-class-method-file-statement"