Skip to content

Commit

Permalink
8338301: Error recovery and reporting should be improved for erroneou…
Browse files Browse the repository at this point in the history
…s implicitly declared classes
  • Loading branch information
lahodaj committed Aug 13, 2024
1 parent 88e28ca commit 475df82
Show file tree
Hide file tree
Showing 2 changed files with 170 additions and 104 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4037,6 +4037,7 @@ public JCTree.JCCompilationUnit parseCompilationUnit() {
seenImport = true;
defs.append(importDeclaration());
} else {
int pos = token.pos;
Comment docComment = token.docComment();
if (firstTypeDecl && !seenImport && !seenPackage) {
docComment = firstToken.docComment();
Expand Down Expand Up @@ -4069,34 +4070,20 @@ public JCTree.JCCompilationUnit parseCompilationUnit() {
}

defs.appendList(semiList.toList());
boolean isTopLevelMethodOrField = false;

// Due to a significant number of existing negative tests
// this code speculatively tests to see if a top level method
// or field can parse. If the method or field can parse then
// it is parsed. Otherwise, parsing continues as though
// implicitly declared classes did not exist and error reporting
// is the same as in the past.
if (Feature.IMPLICIT_CLASSES.allowedInSource(source) && !isDeclaration()) {
final JCModifiers finalMods = mods;
JavacParser speculative = new VirtualParser(this);
List<JCTree> speculativeResult =
speculative.topLevelMethodOrFieldDeclaration(finalMods, null);
if (speculativeResult.head.hasTag(METHODDEF) ||
speculativeResult.head.hasTag(VARDEF)) {
isTopLevelMethodOrField = true;
}
}

if (isTopLevelMethodOrField) {
checkSourceLevel(token.pos, Feature.IMPLICIT_CLASSES);
defs.appendList(topLevelMethodOrFieldDeclaration(mods, docComment));
isImplicitClass = true;
} else {
JCTree def = typeDeclaration(mods, docComment);
if (def instanceof JCExpressionStatement statement)
def = statement.expr;
defs.append(def);
List<JCTree> declarations = classOrInterfaceOrRecordBodyDeclarationRest(pos, mods, null, false, false, docComment);

for (JCTree declaration : declarations) {
if (declaration.hasTag(METHODDEF) ||
declaration.hasTag(VARDEF)) {
checkSourceLevel(pos, Feature.IMPLICIT_CLASSES);
defs.append(declaration);
isImplicitClass = true;
} else if (declaration instanceof JCExpressionStatement statement) {
defs.append(statement.expr);
} else { //TODO: check for initializers; check for constructors(!)
defs.append(declaration);
}
}

mods = null;
Expand Down Expand Up @@ -4721,27 +4708,33 @@ protected List<JCTree> classOrInterfaceOrRecordBodyDeclaration(JCModifiers mods,
Comment dc = token.docComment();
int pos = token.pos;
mods = modifiersOpt(mods);
if (isDeclaration()) {
return List.of(classOrRecordOrInterfaceOrEnumDeclaration(mods, dc));
} else if (token.kind == LBRACE &&
(mods.flags & Flags.StandardFlags & ~Flags.STATIC) == 0 &&
mods.annotations.isEmpty()) {
if (isInterface) {
log.error(DiagnosticFlag.SYNTAX, token.pos, Errors.InitializerNotAllowed);
} else if (isRecord && (mods.flags & Flags.STATIC) == 0) {
log.error(DiagnosticFlag.SYNTAX, token.pos, Errors.InstanceInitializerNotAllowedInRecords);
}
ignoreDanglingComments(); // no declaration with which dangling comments can be associated
return List.of(block(pos, mods.flags));
} else if (isDefiniteStatementStartToken()) {
int startPos = token.pos;
List<JCStatement> statements = blockStatement();
return List.of(syntaxError(startPos,
statements,
Errors.StatementNotExpected));
} else {
return constructorOrMethodOrFieldDeclaration(mods, className, isInterface, isRecord, dc);
}
return classOrInterfaceOrRecordBodyDeclarationRest(pos, mods, className, isInterface, isRecord, dc);
}
}

protected List<JCTree> classOrInterfaceOrRecordBodyDeclarationRest(int pos, JCModifiers mods, Name className,
boolean isInterface,
boolean isRecord,
Comment dc) {
if (isDeclaration()) {
return List.of(classOrRecordOrInterfaceOrEnumDeclaration(mods, dc));
} else if (token.kind == LBRACE &&
(mods.flags & Flags.StandardFlags & ~Flags.STATIC) == 0 &&
mods.annotations.isEmpty()) {
if (isInterface) {
log.error(DiagnosticFlag.SYNTAX, token.pos, Errors.InitializerNotAllowed);
} else if (isRecord && (mods.flags & Flags.STATIC) == 0) {
log.error(DiagnosticFlag.SYNTAX, token.pos, Errors.InstanceInitializerNotAllowedInRecords);
}
ignoreDanglingComments(); // no declaration with which dangling comments can be associated
return List.of(block(pos, mods.flags));
} else if (isDefiniteStatementStartToken()) {
int startPos = token.pos;
List<JCStatement> statements = blockStatement();
log.error(startPos, Errors.StatementNotExpected);
return List.of(F.at(startPos).Erroneous(statements));
} else {
return constructorOrMethodOrFieldDeclaration(mods, className, isInterface, isRecord, dc);
}
}

Expand Down Expand Up @@ -4855,62 +4848,6 @@ private List<JCTree> constructorOrMethodOrFieldDeclaration(JCModifiers mods, Nam
return List.of(syntaxError(token.pos, err, Errors.Expected(LPAREN)));
}

private List<JCTree> topLevelMethodOrFieldDeclaration(JCModifiers mods, Comment dc) throws AssertionError {
int pos = token.pos;
dc = dc == null ? token.docComment() : dc;
List<JCTypeParameter> typarams = typeParametersOpt();

// if there are type parameters but no modifiers, save the start
// position of the method in the modifiers.
if (typarams.nonEmpty() && mods.pos == Position.NOPOS) {
mods.pos = pos;
storeEnd(mods, pos);
}

List<JCAnnotation> annosAfterParams = annotationsOpt(Tag.ANNOTATION);

if (annosAfterParams.nonEmpty()) {
mods.annotations = mods.annotations.appendList(annosAfterParams);
if (mods.pos == Position.NOPOS)
mods.pos = mods.annotations.head.pos;
}

pos = token.pos;
JCExpression type;
boolean isVoid = token.kind == VOID;

if (isVoid) {
type = to(F.at(pos).TypeIdent(TypeTag.VOID));
nextToken();
} else {
type = unannotatedType(false);
}

if (token.kind == IDENTIFIER) {
pos = token.pos;
Name name = ident();

// Method
if (token.kind == LPAREN) {
return List.of(methodDeclaratorRest(pos, mods, type, name, typarams,
false, isVoid, false, dc));
}

// Field
if (!isVoid && typarams.isEmpty() && (token.kind == EQ || token.kind == SEMI)) {
List<JCTree> defs =
variableDeclaratorsRest(pos, mods, type, name, false, dc,
new ListBuffer<JCTree>(), false).toList();
accept(SEMI);
storeEnd(defs.last(), S.prevToken().endPos);

return defs;
}
}

return List.of(F.Erroneous());
}

protected boolean isDeclaration() {
return token.kind == CLASS ||
token.kind == INTERFACE ||
Expand Down
129 changes: 129 additions & 0 deletions test/langtools/tools/javac/ImplicitClass/ErrorRecovery.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
/*
* 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<String> 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<String> 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 testBrokenVariable(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) ;
""");

Files.createDirectories(classes);

List<String> 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<String> 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);
}
}
}

0 comments on commit 475df82

Please sign in to comment.