Skip to content

Commit 0b4a7d5

Browse files
committedAug 29, 2024
8324859: Improve error recovery
Reviewed-by: mcimadamore
1 parent 1383fec commit 0b4a7d5

File tree

2 files changed

+622
-4
lines changed

2 files changed

+622
-4
lines changed
 

‎src/jdk.compiler/share/classes/com/sun/tools/javac/parser/JavacParser.java

+90-3
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
import static com.sun.tools.javac.parser.Tokens.TokenKind.GT;
6060
import static com.sun.tools.javac.parser.Tokens.TokenKind.IMPORT;
6161
import static com.sun.tools.javac.parser.Tokens.TokenKind.LT;
62+
import com.sun.tools.javac.parser.VirtualParser.VirtualScanner;
6263
import static com.sun.tools.javac.tree.JCTree.Tag.*;
6364
import static com.sun.tools.javac.resources.CompilerProperties.Fragments.ImplicitAndExplicitNotAllowed;
6465
import static com.sun.tools.javac.resources.CompilerProperties.Fragments.VarAndExplicitNotAllowed;
@@ -5019,13 +5020,17 @@ protected JCTree methodDeclaratorRest(int pos,
50195020
// Parsing formalParameters sets the receiverParam, if present
50205021
List<JCVariableDecl> params = List.nil();
50215022
List<JCExpression> thrown = List.nil();
5023+
boolean unclosedParameterList;
50225024
if (!isRecord || name != names.init || token.kind == LPAREN) {
50235025
params = formalParameters();
5026+
unclosedParameterList = token.pos == endPosTable.errorEndPos;
50245027
if (!isVoid) type = bracketsOpt(type);
50255028
if (token.kind == THROWS) {
50265029
nextToken();
50275030
thrown = qualidentList(true);
50285031
}
5032+
} else {
5033+
unclosedParameterList = false;
50295034
}
50305035

50315036
saveDanglingDocComments(dc);
@@ -5039,14 +5044,18 @@ protected JCTree methodDeclaratorRest(int pos,
50395044
if (token.kind == DEFAULT) {
50405045
accept(DEFAULT);
50415046
defaultValue = annotationValue();
5047+
accept(SEMI);
50425048
} else {
50435049
defaultValue = null;
5050+
accept(SEMI, tk -> Errors.Expected2(LBRACE, SEMI));
50445051
}
5045-
accept(SEMI);
50465052
if (token.pos <= endPosTable.errorEndPos) {
50475053
// error recovery
5048-
skip(false, true, false, false);
5049-
if (token.kind == LBRACE) {
5054+
// look if there is a probable missing opening brace,
5055+
// and if yes, parse as a block
5056+
boolean parseAsBlock = openingBraceMissing(unclosedParameterList);
5057+
5058+
if (parseAsBlock) {
50505059
body = block();
50515060
}
50525061
}
@@ -5063,6 +5072,84 @@ protected JCTree methodDeclaratorRest(int pos,
50635072
}
50645073
}
50655074

5075+
/**
5076+
* After seeing a method header, and not seeing an opening left brace,
5077+
* attempt to estimate if acting as if the left brace was present and
5078+
* parsing the upcoming code will get better results than not parsing
5079+
* the code as a block.
5080+
*
5081+
* The estimate is as follows:
5082+
* - tokens are skipped until member, statement or identifier is found,
5083+
* - then, if there is a left brace, parse as a block,
5084+
* - otherwise, if the head was broken, do not parse as a block,
5085+
* - otherwise, look at the next token and:
5086+
* - if it definitelly starts a statement, parse as a block,
5087+
* - otherwise, if it is a closing/right brace, count opening and closing
5088+
* braces in the rest of the file, to see if imaginarily "adding" an opening
5089+
* brace would lead to a balanced count - if yes, parse as a block,
5090+
* - otherwise, speculatively parse the following code as a block, and if
5091+
* it contains statements that cannot be members, parse as a block,
5092+
* - otherwise, don't parse as a block.
5093+
*
5094+
* @param unclosedParameterList whether there was a serious problem in the
5095+
* parameters list
5096+
* @return true if and only if the following code should be parsed as a block.
5097+
*/
5098+
private boolean openingBraceMissing(boolean unclosedParameterList) {
5099+
skip(false, true, !unclosedParameterList, !unclosedParameterList);
5100+
5101+
if (token.kind == LBRACE) {
5102+
return true;
5103+
} else if (unclosedParameterList) {
5104+
return false;
5105+
} else {
5106+
return switch (token.kind) {
5107+
//definitelly sees a statement:
5108+
case CASE, DEFAULT, IF, FOR, WHILE, DO, TRY, SWITCH,
5109+
RETURN, THROW, BREAK, CONTINUE, ELSE, FINALLY,
5110+
CATCH, THIS, SUPER, NEW -> true;
5111+
case RBRACE -> {
5112+
//check if adding an opening brace would balance out
5113+
//the opening and closing braces:
5114+
int braceBalance = 1;
5115+
VirtualScanner virtualScanner = new VirtualScanner(S);
5116+
5117+
virtualScanner.nextToken();
5118+
5119+
while (virtualScanner.token().kind != TokenKind.EOF) {
5120+
switch (virtualScanner.token().kind) {
5121+
case LBRACE -> braceBalance++;
5122+
case RBRACE -> braceBalance--;
5123+
}
5124+
virtualScanner.nextToken();
5125+
}
5126+
5127+
yield braceBalance == 0;
5128+
}
5129+
default -> {
5130+
//speculatively try to parse as a block, and check
5131+
//if the result would suggest there is a block
5132+
//e.g.: it contains a statement that is not
5133+
//a member declaration
5134+
JavacParser speculative = new VirtualParser(this);
5135+
JCBlock speculativeResult =
5136+
speculative.block();
5137+
if (!speculativeResult.stats.isEmpty()) {
5138+
JCStatement last = speculativeResult.stats.last();
5139+
yield !speculativeResult.stats.stream().allMatch(s -> s.hasTag(VARDEF) ||
5140+
s.hasTag(CLASSDEF) ||
5141+
s.hasTag(BLOCK) ||
5142+
s == last) ||
5143+
!(last instanceof JCExpressionStatement exprStatement &&
5144+
exprStatement.expr.hasTag(ERRONEOUS));
5145+
} else {
5146+
yield false;
5147+
}
5148+
}
5149+
};
5150+
}
5151+
}
5152+
50665153
/** QualidentList = [Annotations] Qualident {"," [Annotations] Qualident}
50675154
*/
50685155
List<JCExpression> qualidentList(boolean allowAnnos) {

0 commit comments

Comments
 (0)