8228647: Broken enum produce inconvenient errors and AST

Improving error recovery for misplace members in enums.

Reviewed-by: vromero
This commit is contained in:
Jan Lahoda 2019-08-13 10:27:33 +02:00
parent 6e86f5b47b
commit 36ae680f2a
8 changed files with 316 additions and 20 deletions

View file

@ -3756,23 +3756,59 @@ public class JavacParser implements Parser {
List<JCTree> enumBody(Name enumName) {
accept(LBRACE);
ListBuffer<JCTree> defs = new ListBuffer<>();
boolean wasSemi = false;
boolean hasStructuralErrors = false;
boolean wasError = false;
if (token.kind == COMMA) {
nextToken();
} else if (token.kind != RBRACE && token.kind != SEMI) {
defs.append(enumeratorDeclaration(enumName));
while (token.kind == COMMA) {
nextToken();
if (token.kind == RBRACE || token.kind == SEMI) break;
defs.append(enumeratorDeclaration(enumName));
}
if (token.kind != SEMI && token.kind != RBRACE) {
defs.append(syntaxError(token.pos, Errors.Expected3(COMMA, RBRACE, SEMI)));
nextToken();
}
}
if (token.kind == SEMI) {
wasSemi = true;
nextToken();
} else if (token.kind != RBRACE) {
reportSyntaxError(S.prevToken().endPos,
Errors.Expected2(RBRACE, SEMI));
wasError = true;
}
}
while (token.kind != RBRACE && token.kind != EOF) {
if (token.kind == SEMI) {
accept(SEMI);
wasSemi = true;
if (token.kind == RBRACE || token.kind == EOF) break;
}
EnumeratorEstimate memberType = estimateEnumeratorOrMember(enumName);
if (memberType == EnumeratorEstimate.UNKNOWN) {
memberType = wasSemi ? EnumeratorEstimate.MEMBER
: EnumeratorEstimate.ENUMERATOR;
}
if (memberType == EnumeratorEstimate.ENUMERATOR) {
wasError = false;
if (wasSemi && !hasStructuralErrors) {
reportSyntaxError(token.pos, Errors.EnumConstantNotExpected);
hasStructuralErrors = true;
}
defs.append(enumeratorDeclaration(enumName));
if (token.pos <= endPosTable.errorEndPos) {
// error recovery
skip(false, true, true, false);
} else {
if (token.kind != RBRACE && token.kind != SEMI && token.kind != EOF) {
if (token.kind == COMMA) {
nextToken();
} else {
setErrorEndPos(token.pos);
reportSyntaxError(S.prevToken().endPos,
Errors.Expected3(COMMA, RBRACE, SEMI));
wasError = true;
}
}
}
} else {
if (!wasSemi && !hasStructuralErrors && !wasError) {
reportSyntaxError(token.pos, Errors.EnumConstantExpected);
hasStructuralErrors = true;
}
wasError = false;
defs.appendList(classOrInterfaceBodyDeclaration(enumName,
false));
if (token.pos <= endPosTable.errorEndPos) {
@ -3785,6 +3821,28 @@ public class JavacParser implements Parser {
return defs.toList();
}
private EnumeratorEstimate estimateEnumeratorOrMember(Name enumName) {
if (token.kind == TokenKind.IDENTIFIER && token.name() != enumName) {
Token next = S.token(1);
switch (next.kind) {
case LPAREN: case LBRACE: case COMMA: case SEMI:
return EnumeratorEstimate.ENUMERATOR;
}
}
switch (token.kind) {
case IDENTIFIER: case MONKEYS_AT: case LT:
return EnumeratorEstimate.UNKNOWN;
default:
return EnumeratorEstimate.MEMBER;
}
}
private enum EnumeratorEstimate {
ENUMERATOR,
MEMBER,
UNKNOWN;
}
/** EnumeratorDeclaration = AnnotationsOpt [TypeArguments] IDENTIFIER [ Arguments ] [ "{" ClassBody "}" ]
*/
JCTree enumeratorDeclaration(Name enumName) {

View file

@ -2158,6 +2158,12 @@ compiler.err.expected3=\
compiler.err.premature.eof=\
reached end of file while parsing
compiler.err.enum.constant.expected=\
enum constant expected here
compiler.err.enum.constant.not.expected=\
enum constant not expected here
## The following are related in form, but do not easily fit the above paradigm.
compiler.err.expected.module=\
''module'' expected

View file

@ -10,5 +10,8 @@ public enum T4994049 {
FOO {
}
BAR;
BAR1,
BAR2(),
BAR3 {},
BAR4() {};
}

View file

@ -1,2 +1,2 @@
T4994049.java:13:5: compiler.err.expected3: ',', '}', ';'
T4994049.java:11:6: compiler.err.expected3: ',', '}', ';'
1 error

View file

@ -0,0 +1,30 @@
/*
* Copyright (c) 2019, 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.enum.constant.expected
enum EnumConstantExpected {
A,
void test() {}
;
}

View file

@ -0,0 +1,29 @@
/*
* Copyright (c) 2019, 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.enum.constant.not.expected
enum EnumConstantNotExpected {
;
A;
}

View file

@ -1,7 +1,4 @@
EnumMembersOrder.java:11:16: compiler.err.expected: ')'
EnumMembersOrder.java:11:17: compiler.err.expected3: ',', '}', ';'
EnumMembersOrder.java:11:18: compiler.err.expected: '}'
EnumMembersOrder.java:11:31: compiler.err.expected3: class, interface, enum
EnumMembersOrder.java:17:13: compiler.err.expected3: class, interface, enum
EnumMembersOrder.java:19:1: compiler.err.expected3: class, interface, enum
6 errors
EnumMembersOrder.java:11:18: compiler.err.expected3: ',', '}', ';'
EnumMembersOrder.java:11:20: compiler.err.enum.constant.expected
3 errors

View file

@ -1291,6 +1291,179 @@ public class JavacParserTest extends TestCase {
assertTrue("testAnalyzeParensWithComma2", found[0]);
}
@Test
void testBrokenEnum1() throws IOException {
assert tool != null;
String code = "package test; class Test { enum E { A, B, C. D, E, F; } }";
StringWriter output = new StringWriter();
JavacTaskImpl ct = (JavacTaskImpl) tool.getTask(output, fm, null, List.of("-XDrawDiagnostics"),
null, Arrays.asList(new MyFileObject(code)));
CompilationUnitTree cut = ct.parse().iterator().next();
List<String> actual = List.of(output.toString().split("\r?\n"));
List<String> expected = List.of("Test.java:1:44: compiler.err.expected3: ',', '}', ';'");
assertEquals("The expected and actual errors do not match, actual errors: " + actual,
actual,
expected);
String actualAST = cut.toString().replaceAll("\r*\n", "\n");
String expectedAST = "package test;\n" +
"\n" +
"class Test {\n" +
" \n" +
" enum E {\n" +
" /*public static final*/ A /* = new E() */ /*enum*/ ,\n" +
" /*public static final*/ B /* = new E() */ /*enum*/ ,\n" +
" /*public static final*/ C /* = new E() */ /*enum*/ ,\n" +
" /*public static final*/ D /* = new E() */ /*enum*/ ,\n" +
" /*public static final*/ E /* = new E() */ /*enum*/ ,\n" +
" /*public static final*/ F /* = new E() */ /*enum*/ ;\n" +
" (ERROR) <error>;\n" +
" }\n" +
"}";
assertEquals("The expected and actual AST do not match, actual AST: " + actualAST,
actualAST,
expectedAST);
}
@Test
void testBrokenEnum2() throws IOException {
assert tool != null;
String code = "package test; class Test { enum E { A, B, C void t() {} } }";
StringWriter output = new StringWriter();
JavacTaskImpl ct = (JavacTaskImpl) tool.getTask(output, fm, null, List.of("-XDrawDiagnostics"),
null, Arrays.asList(new MyFileObject(code)));
CompilationUnitTree cut = ct.parse().iterator().next();
List<String> actual = List.of(output.toString().split("\r?\n"));
List<String> expected = List.of("Test.java:1:44: compiler.err.expected3: ',', '}', ';'");
assertEquals("The expected and actual errors do not match, actual errors: " + actual,
actual,
expected);
String actualAST = cut.toString().replaceAll("\r*\n", "\n");
String expectedAST = "package test;\n" +
"\n" +
"class Test {\n" +
" \n" +
" enum E {\n" +
" /*public static final*/ A /* = new E() */ /*enum*/ ,\n" +
" /*public static final*/ B /* = new E() */ /*enum*/ ,\n" +
" /*public static final*/ C /* = new E() */ /*enum*/ ;\n" +
" \n" +
" void t() {\n" +
" }\n" +
" }\n" +
"}";
assertEquals("The expected and actual AST do not match, actual AST: " + actualAST,
actualAST,
expectedAST);
}
@Test
void testBrokenEnum3() throws IOException {
assert tool != null;
String code = "package test; class Test { enum E { , void t() {} } }";
StringWriter output = new StringWriter();
JavacTaskImpl ct = (JavacTaskImpl) tool.getTask(output, fm, null, List.of("-XDrawDiagnostics"),
null, Arrays.asList(new MyFileObject(code)));
CompilationUnitTree cut = ct.parse().iterator().next();
List<String> actual = List.of(output.toString().split("\r?\n"));
List<String> expected = List.of("Test.java:1:38: compiler.err.expected2: '}', ';'");
assertEquals("The expected and actual errors do not match, actual errors: " + actual,
actual,
expected);
String actualAST = cut.toString().replaceAll("\r*\n", "\n");
String expectedAST = "package test;\n" +
"\n" +
"class Test {\n" +
" \n" +
" enum E {\n" +
";\n" +
" \n" +
" void t() {\n" +
" }\n" +
" }\n" +
"}";
assertEquals("The expected and actual AST do not match, actual AST: " + actualAST,
actualAST,
expectedAST);
}
@Test
void testBrokenEnum4() throws IOException {
assert tool != null;
String code = "package test; class Test { enum E { A, B, C, void t() {} } }";
StringWriter output = new StringWriter();
JavacTaskImpl ct = (JavacTaskImpl) tool.getTask(output, fm, null, List.of("-XDrawDiagnostics"),
null, Arrays.asList(new MyFileObject(code)));
CompilationUnitTree cut = ct.parse().iterator().next();
List<String> actual = List.of(output.toString().split("\r?\n"));
List<String> expected = List.of("Test.java:1:46: compiler.err.enum.constant.expected");
assertEquals("The expected and actual errors do not match, actual errors: " + actual,
actual,
expected);
String actualAST = cut.toString().replaceAll("\r*\n", "\n");
String expectedAST = "package test;\n" +
"\n" +
"class Test {\n" +
" \n" +
" enum E {\n" +
" /*public static final*/ A /* = new E() */ /*enum*/ ,\n" +
" /*public static final*/ B /* = new E() */ /*enum*/ ,\n" +
" /*public static final*/ C /* = new E() */ /*enum*/ ;\n" +
" \n" +
" void t() {\n" +
" }\n" +
" }\n" +
"}";
assertEquals("The expected and actual AST do not match, actual AST: " + actualAST,
actualAST,
expectedAST);
}
@Test
void testBrokenEnum5() throws IOException {
assert tool != null;
String code = "package test; class Test { enum E { A; void t() {} B; } }";
StringWriter output = new StringWriter();
JavacTaskImpl ct = (JavacTaskImpl) tool.getTask(output, fm, null, List.of("-XDrawDiagnostics"),
null, Arrays.asList(new MyFileObject(code)));
CompilationUnitTree cut = ct.parse().iterator().next();
List<String> actual = List.of(output.toString().split("\r?\n"));
List<String> expected = List.of("Test.java:1:52: compiler.err.enum.constant.not.expected");
assertEquals("The expected and actual errors do not match, actual errors: " + actual,
actual,
expected);
String actualAST = cut.toString().replaceAll("\r*\n", "\n");
String expectedAST = "package test;\n" +
"\n" +
"class Test {\n" +
" \n" +
" enum E {\n" +
" /*public static final*/ A /* = new E() */ /*enum*/ ,\n" +
" /*public static final*/ B /* = new E() */ /*enum*/ ;\n" +
" \n" +
" void t() {\n" +
" }\n" +
" }\n" +
"}";
assertEquals("The expected and actual AST do not match, actual AST: " + actualAST,
actualAST,
expectedAST);
}
void run(String[] args) throws Exception {
int passed = 0, failed = 0;
final Pattern p = (args != null && args.length > 0)