RFR: 8324859: Improve error recovery
Jan Lahoda
jlahoda at openjdk.org
Mon Aug 19 07:35:50 UTC 2024
On Mon, 8 Jul 2024 10:03:03 GMT, Jan Lahoda <jlahoda at openjdk.org> wrote:
> Consider code like:
>
> package tests;
> public class TestB {
> public static boolean test() // <--- missing open brace
> return true;
> }
> public static boolean test2() {
> return true;
> }
> }
>
>
> In JDK 17, it used to produce 3 compile-time errors:
>
> $ javac /tmp/TestB.java
> /tmp/TestB.java:3: error: ';' expected
> public static boolean test() // <--- missing open brace
> ^
> /tmp/TestB.java:6: error: class, interface, enum, or record expected
> public static boolean test2() {
> ^
> /tmp/TestB.java:8: error: class, interface, enum, or record expected
> }
> ^
> 3 errors
>
>
> Currently, it produces 4:
>
> $ javac /tmp/TestB.java
> /tmp/TestB.java:3: error: ';' expected
> public static boolean test() // <--- missing open brace
> ^
> /tmp/TestB.java:6: error: implicitly declared classes are a preview feature and are disabled by default.
> public static boolean test2() {
> ^
> (use --enable-preview to enable implicitly declared classes)
> /tmp/TestB.java:9: error: class, interface, enum, or record expected
> }
> ^
> /tmp/TestB.java:1: error: implicitly declared class should not have package declaration
> package tests;
> ^
> 4 errors
>
>
> Neither of these is particularly nice. The common property is that the javac's parser "de-synchronizes" on the missing opening brace (`{`), and consumes the first closing brace (`}`) as a closing brace for the class (not for the method), causing all the follow-up behavior.
>
> In general, the javac's parser handles missing closing braces fairly well - it skips tokens until it find something it can synchronize on, and continues. But, it does not handle missing opening braces as well, and has tendency to get lost in the input.
>
> For the case above, it would be much better if javac parsed the code following the missing opening brace as a block.
>
> This patch is attempting to do that, without making other cases I was able to find (much) worse. The overall approach is to skip tokens until a statement or member start is found, and then look at what follows:
> - if it is an opening brace, parse the follow up as a block
> - if it is a statement keyword (like `if`), parse the follow up as a block,
> - if it is a closing brace, see if injecting a virtual opening brace at the current position would balance the opening/closing braces in the rest of the file - if yes, parse the follow up as a block,
> - otherwise, try to speculatively parse the follow up - if it look...
FWIW, this is a comparison of errors produced by javac from JDK 17 (`javac-17`) and javac with this patch (`javac-patched`), for a set of inputs:
$ cat TestB.java
package tests;
public class TestB {
public static boolean test() // missing open brace
return true;
}
public static boolean test2() {
return true;
}
}
$ javac-17 TestB.java
TestB.java:3: error: ';' expected
public static boolean test() // missing open brace
^
TestB.java:6: error: class, interface, enum, or record expected
public static boolean test2() {
^
TestB.java:8: error: class, interface, enum, or record expected
}
^
3 errors
$ javac-patched TestB.java
TestB.java:3: error: '{' or ';' expected
public static boolean test() // missing open brace
^
1 error
$ cat TestB.java
package tests;
public class TestB {
public static boolean test() // missing open brace
public static boolean test2() {
return true;
}
}
$ javac-17 TestB.java
TestB.java:3: error: ';' expected
public static boolean test() // missing open brace
^
1 error
$ javac-patched TestB.java
TestB.java:3: error: '{' or ';' expected
public static boolean test() // missing open brace
^
1 error
$ cat TestB.java
package tests;
public class TestB {
public static boolean test() // missing open brace
}
$ javac-17 TestB.java
TestB.java:3: error: ';' expected
public static boolean test() // missing open brace
^
1 error
$ javac-patched TestB.java
TestB.java:3: error: '{' or ';' expected
public static boolean test() // missing open brace
^
1 error
$ cat TestB.java
package tests;
public class TestB {
public static boolean test() // missing open brace
}
}
$ javac-17 TestB.java
TestB.java:3: error: ';' expected
public static boolean test() // missing open brace
^
TestB.java:5: error: class, interface, enum, or record expected
}
^
2 errors
$ javac-patched TestB.java
TestB.java:3: error: '{' or ';' expected
public static boolean test() // missing open brace
^
1 error
$ cat TestB.java
package tests;
public class TestB {
public static boolean test(String,
}
class T {}
$ javac-17 TestB.java
TestB.java:3: error: <identifier> expected
public static boolean test(String,
^
TestB.java:4: error: illegal start of type
}
^
2 errors
$ javac-patched TestB.java
TestB.java:3: error: <identifier> expected
public static boolean test(String,
^
TestB.java:4: error: illegal start of type
}
^
2 errors
$ cat TestB.java
package tests;
public class TestB {
private Object testMethod(final String arg1 final String arg2) {
return null;
}
}
$ javac-17 TestB.java
TestB.java:3: error: ',', ')', or '[' expected
private Object testMethod(final String arg1 final String arg2) {
^
TestB.java:3: error: ';' expected
private Object testMethod(final String arg1 final String arg2) {
^
2 errors
$ javac-patched TestB.java
TestB.java:3: error: ',', ')', or '[' expected
private Object testMethod(final String arg1 final String arg2) {
^
TestB.java:3: error: ';' expected
private Object testMethod(final String arg1 final String arg2) {
^
2 errors
$ cat TestB.java
package tests;
public @interface A {
public String value() default ""
}
$ javac-17 TestB.java
TestB.java:3: error: ';' expected
public String value() default ""
^
1 error
$ javac-patched TestB.java
TestB.java:3: error: ';' expected
public String value() default ""
^
1 error
$ cat TestB.java
package tests;
public class TestB {
public static boolean test() // missing open brace
String s = "";
return s.isEmpty();
}
public static boolean test2() {
return true;
}
}
$ javac-17 TestB.java
TestB.java:3: error: ';' expected
public static boolean test() // missing open brace
^
TestB.java:5: error: illegal start of type
return s.isEmpty();
^
TestB.java:5: error: <identifier> expected
return s.isEmpty();
^
TestB.java:7: error: class, interface, enum, or record expected
public static boolean test2() {
^
TestB.java:9: error: class, interface, enum, or record expected
}
^
5 errors
$ javac-patched TestB.java
TestB.java:3: error: '{' or ';' expected
public static boolean test() // missing open brace
^
1 error
$ cat TestB.java
package tests;
public class TestB {
public static boolean test() // missing open brace
String s = ""; //field declaration
public static boolean test2() {
return true;
}
}
$ javac-17 TestB.java
TestB.java:3: error: ';' expected
public static boolean test() // missing open brace
^
1 error
$ javac-patched TestB.java
TestB.java:3: error: '{' or ';' expected
public static boolean test() // missing open brace
^
1 error
$ cat TestB.java
package tests;
public class TestB {
public static boolean test() // missing open brace
final String s = "";
return s.isEmpty();
}
public static boolean test2() {
return true;
}
}
$ javac-17 TestB.java
TestB.java:3: error: ';' expected
public static boolean test() // missing open brace
^
TestB.java:5: error: illegal start of type
return s.isEmpty();
^
TestB.java:5: error: <identifier> expected
return s.isEmpty();
^
TestB.java:7: error: class, interface, enum, or record expected
public static boolean test2() {
^
TestB.java:9: error: class, interface, enum, or record expected
}
^
5 errors
$ javac-patched TestB.java
TestB.java:3: error: '{' or ';' expected
public static boolean test() // missing open brace
^
1 error
$ cat TestB.java
package tests;
public class TestB {
public static boolean test() // missing open brace
final String s = ""; //field declaration?
public static boolean test2() {
return true;
}
}
$ javac-17 TestB.java
TestB.java:3: error: ';' expected
public static boolean test() // missing open brace
^
1 error
$ javac-patched TestB.java
TestB.java:3: error: '{' or ';' expected
public static boolean test() // missing open brace
^
1 error
-------------
PR Comment: https://git.openjdk.org/jdk/pull/20070#issuecomment-2295861894
More information about the compiler-dev
mailing list