[ 
https://issues.apache.org/jira/browse/GROOVY-12169?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
 ]

Daniel Sun updated GROOVY-12169:
--------------------------------
    Description: 
h3. Problem

When Groovy source is missing a closing delimiter (right paren, right bracket, 
or right brace), the ANTLR4 parser often reports a generic *Unexpected input* 
message at a *misleading* location (commonly the opening token or an earlier 
construct), instead of a clear *Missing ...* message at the place where the 
closer should be inserted.

That makes simple typos hard to diagnose, especially with:
 * casts and parenthesised expressions (missing right paren)
 * lists, maps, and indexing (missing right bracket, including safe-index)
 * blocks, classes, and closures (missing right brace)

h3. Examples (before)
 * Source:
{noformat}
println ((int 123)
{noformat}
Report: Unexpected input for open-paren at the *opening* parenthesis

 * Source:
{noformat}
[1, 2
{noformat}
Report: Unexpected input: '<EOF>' at end of input

 * Source:
{noformat}
foo[1
{noformat}
Report: Unexpected input for the open-bracket token

 * Source:
{noformat}
def m() {
    println 1
{noformat}
Report: Unexpected input far from the missing brace

 * Source:
{noformat}
foo([1, 2)
{noformat}
Report: Unexpected input for open-paren (real issue: missing right bracket 
before right paren)

h3. Root cause
 * ANTLR4 does not recover missing tokens as helpfully as the old Antlr2 path.
 * Grammar-level error alternatives (for example on {{{}rparen{}}}) can yield a 
*Missing right-paren* message, but they enlarge the ATN and hurt 
{*}successful{*}-parse performance. They were removed in the GROOVY-9588 work 
for that reason.
 * With {{BailErrorStrategy}} plus generic mismatch reporting, prediction often 
abandons a deep delimited alternative and falls back to a shorter parse, so the 
surface error points far from the real omission.

h3. Goal

Report {*}Missing right-paren{*}, {*}Missing right-bracket{*}, or *Missing 
right-brace* with an accurate caret when the token stream clearly indicates a 
missing or incomplete closer — *without* reintroducing grammar error 
alternatives on the hot (successful) parse path.
h3. Approach

Error-path-only diagnostics wired into the existing error strategy:
 * {{DescriptiveErrorStrategy}} — on input mismatch / no viable alternative, 
prefer a missing-delimiter hit over the generic message.
 * {{MissingDelimiterDiagnostic}} — one linear scan of the fully filled token 
stream *after* failure; no work on successful parses.

Detection order (most specific first):

*1. Sole expected closer* — RecognitionException whose *sole* expected token is 
RPAREN, RBRACK, or RBRACE, and the corresponding open-depth is still positive. 
This avoids false positives where delimiters are balanced but an intermediate 
token is wrong, for example:
{noformat}
foo(1;2;3)
{noformat}
*2. Cast pattern* — open-paren, type, then expression-start without the cast's 
close-paren, for example:
{noformat}
(int 123
{noformat}
*3. Delimiter stack* — walk openers for paren, bracket, safe-index, and brace; 
report the *innermost* missing closer on mismatch or at EOF, for example:
{noformat}
foo([1, 2)
{noformat}
(missing right bracket before the right paren)

The token stream is force-filled to EOF before scanning so a trailing closer is 
visible even when the parser failed earlier inside the construct.
h3. Expected result (after)
 * Source:
{noformat}
println ((int 123)
{noformat}
Expected: *Missing right-paren* at 123

 * Source:
{noformat}
def x() {
    println((int) 123
}
{noformat}
Expected: *Missing right-paren* after 123

 * Source:
{noformat}
def m( {
}
{noformat}
Expected: *Missing right-paren* at the open brace

 * Source:
{noformat}
foo(1, 2
{noformat}
Expected: *Missing right-paren* at end of line

 * Source:
{noformat}
[1, 2
{noformat}
Expected: *Missing right-bracket* at end of line

 * Source:
{noformat}
foo[1
{noformat}
Expected: *Missing right-bracket* after 1

 * Source:
{noformat}
def x = [[1, 2]
{noformat}
Expected: *Missing right-bracket* after the inner closing bracket

 * Source:
{noformat}
foo([1, 2)
{noformat}
Expected: *Missing right-bracket* before the closing paren

 * Source:
{noformat}
a?[0
{noformat}
Expected: *Missing right-bracket* after 0

 * Source:
{noformat}
def m() {
    println 1
{noformat}
Expected: *Missing right-brace* after 1

 * Source:
{noformat}
class C {
    def x
{noformat}
Expected: *Missing right-brace* after x

 * Source:
{noformat}
def c = { it
{noformat}
Expected: *Missing right-brace* after it

False-positive guard (delimiters balanced; must *not* report a 
Missing-delimiter message):
{noformat}
[].bar(1;2;3)
{noformat}
h3. Compatibility and performance
 * No grammar, lexer, or public API changes — valid programs parse as before.
 * Successful path remains {{BailErrorStrategy}} with no ATN error alternatives.
 * Extra work is a single O\(n\) token scan only after a recognition failure.

  was:
h3. Problem

When a Groovy source is missing a closing parenthesis {{{}){}}}, the ANTLR4 
parser often reports a generic *Unexpected input* message at a *misleading* 
location (frequently the opening {{(}} or an earlier token), instead of 
*Missing ')'* at the place where {{)}} should be inserted or where the 
construct first went wrong.

This makes simple typos hard to fix, especially with casts, method calls, and 
formal parameter lists.

h3. Root cause
 * ANTLR4 does not recover missing tokens as helpfully as the old Antlr2 path.
 * Grammar-level *error alternatives* on {{rparen}} can produce {*}Missing 
')'{*}, but they enlarge the ATN and hurt successful-parse performance. They 
were removed in the GROOVY-9588 work for that reason.
 * With {{BailErrorStrategy}} + generic mismatch reporting, prediction often 
fails a deep parenthesised alternative and falls back to a shorter parse, so 
the surface error points far from the real omission.

h3. Goal

Report *Missing ')'* with an accurate caret when the token stream clearly 
indicates a missing or incomplete {{{}){}}}, *without* reintroducing grammar 
error alternatives on the hot (successful) parse path.
h3. Approach

Error-path-only diagnostics in the existing error strategy:
 * {{DescriptiveErrorStrategy}} — on {{InputMismatch}} / {{{}NoViableAlt{}}}, 
prefer a missing-paren diagnostic over the generic message.
 * {{MissingParenDiagnostic}} — single linear scan of the (fully filled) token 
stream after failure; *no* work on successful parses.

Detection order (most specific first):
 # RecognitionException whose *sole* expected token is {{{}RPAREN{}}}, and net 
paren depth > 0 (avoids false positives like {{foo(1;2;3)}} where parens are 
balanced but {{;}} is wrong).
 # Cast / parenthesised-type pattern: {{(type <expr-start>}} without the cast's 
{{)}} (e.g. {{{}(int 123){}}}).
 # Unclosed {{(}} at EOF or at a structural hard-stop such as an outer 
{{{}}{}}}.

Token stream is force-filled to EOF before scanning so a trailing {{)}} is 
visible even when the parser failed earlier inside the list.
h3. Expected result (after)
||Source||Expected message||
|{{println ((int 123)}}|{{Missing ')'}} at the start of {{123}}|
|{\{def x() { println((int) 123 }}}|{{Missing ')'}} at the insertion point 
after {{123}}|
|{\{def m( { }}}|{{Missing ')'}} at {{'
Unknown macro: \{'}
}|
|{{foo(1, 2}}|{{Missing ')'}} at end of line|
|{{def f(int x}}|{{Missing ')'}} after {{x}}|
|{{println ((int) 123}}|{{Missing ')'}} at end of line|

Balanced but invalid constructs (e.g. {{{}[].bar(1;2;3){}}}) must *not* be 
rewritten as {*}Missing ')'{*}; keep the generic unexpected-input (or other) 
message.


> Improve syntax error messages and caret positions for missing ')', ']', and 
> '}'
> -------------------------------------------------------------------------------
>
>                 Key: GROOVY-12169
>                 URL: https://issues.apache.org/jira/browse/GROOVY-12169
>             Project: Groovy
>          Issue Type: Improvement
>            Reporter: Daniel Sun
>            Priority: Major
>
> h3. Problem
> When Groovy source is missing a closing delimiter (right paren, right 
> bracket, or right brace), the ANTLR4 parser often reports a generic 
> *Unexpected input* message at a *misleading* location (commonly the opening 
> token or an earlier construct), instead of a clear *Missing ...* message at 
> the place where the closer should be inserted.
> That makes simple typos hard to diagnose, especially with:
>  * casts and parenthesised expressions (missing right paren)
>  * lists, maps, and indexing (missing right bracket, including safe-index)
>  * blocks, classes, and closures (missing right brace)
> h3. Examples (before)
>  * Source:
> {noformat}
> println ((int 123)
> {noformat}
> Report: Unexpected input for open-paren at the *opening* parenthesis
>  * Source:
> {noformat}
> [1, 2
> {noformat}
> Report: Unexpected input: '<EOF>' at end of input
>  * Source:
> {noformat}
> foo[1
> {noformat}
> Report: Unexpected input for the open-bracket token
>  * Source:
> {noformat}
> def m() {
>     println 1
> {noformat}
> Report: Unexpected input far from the missing brace
>  * Source:
> {noformat}
> foo([1, 2)
> {noformat}
> Report: Unexpected input for open-paren (real issue: missing right bracket 
> before right paren)
> h3. Root cause
>  * ANTLR4 does not recover missing tokens as helpfully as the old Antlr2 path.
>  * Grammar-level error alternatives (for example on {{{}rparen{}}}) can yield 
> a *Missing right-paren* message, but they enlarge the ATN and hurt 
> {*}successful{*}-parse performance. They were removed in the GROOVY-9588 work 
> for that reason.
>  * With {{BailErrorStrategy}} plus generic mismatch reporting, prediction 
> often abandons a deep delimited alternative and falls back to a shorter 
> parse, so the surface error points far from the real omission.
> h3. Goal
> Report {*}Missing right-paren{*}, {*}Missing right-bracket{*}, or *Missing 
> right-brace* with an accurate caret when the token stream clearly indicates a 
> missing or incomplete closer — *without* reintroducing grammar error 
> alternatives on the hot (successful) parse path.
> h3. Approach
> Error-path-only diagnostics wired into the existing error strategy:
>  * {{DescriptiveErrorStrategy}} — on input mismatch / no viable alternative, 
> prefer a missing-delimiter hit over the generic message.
>  * {{MissingDelimiterDiagnostic}} — one linear scan of the fully filled token 
> stream *after* failure; no work on successful parses.
> Detection order (most specific first):
> *1. Sole expected closer* — RecognitionException whose *sole* expected token 
> is RPAREN, RBRACK, or RBRACE, and the corresponding open-depth is still 
> positive. This avoids false positives where delimiters are balanced but an 
> intermediate token is wrong, for example:
> {noformat}
> foo(1;2;3)
> {noformat}
> *2. Cast pattern* — open-paren, type, then expression-start without the 
> cast's close-paren, for example:
> {noformat}
> (int 123
> {noformat}
> *3. Delimiter stack* — walk openers for paren, bracket, safe-index, and 
> brace; report the *innermost* missing closer on mismatch or at EOF, for 
> example:
> {noformat}
> foo([1, 2)
> {noformat}
> (missing right bracket before the right paren)
> The token stream is force-filled to EOF before scanning so a trailing closer 
> is visible even when the parser failed earlier inside the construct.
> h3. Expected result (after)
>  * Source:
> {noformat}
> println ((int 123)
> {noformat}
> Expected: *Missing right-paren* at 123
>  * Source:
> {noformat}
> def x() {
>     println((int) 123
> }
> {noformat}
> Expected: *Missing right-paren* after 123
>  * Source:
> {noformat}
> def m( {
> }
> {noformat}
> Expected: *Missing right-paren* at the open brace
>  * Source:
> {noformat}
> foo(1, 2
> {noformat}
> Expected: *Missing right-paren* at end of line
>  * Source:
> {noformat}
> [1, 2
> {noformat}
> Expected: *Missing right-bracket* at end of line
>  * Source:
> {noformat}
> foo[1
> {noformat}
> Expected: *Missing right-bracket* after 1
>  * Source:
> {noformat}
> def x = [[1, 2]
> {noformat}
> Expected: *Missing right-bracket* after the inner closing bracket
>  * Source:
> {noformat}
> foo([1, 2)
> {noformat}
> Expected: *Missing right-bracket* before the closing paren
>  * Source:
> {noformat}
> a?[0
> {noformat}
> Expected: *Missing right-bracket* after 0
>  * Source:
> {noformat}
> def m() {
>     println 1
> {noformat}
> Expected: *Missing right-brace* after 1
>  * Source:
> {noformat}
> class C {
>     def x
> {noformat}
> Expected: *Missing right-brace* after x
>  * Source:
> {noformat}
> def c = { it
> {noformat}
> Expected: *Missing right-brace* after it
> False-positive guard (delimiters balanced; must *not* report a 
> Missing-delimiter message):
> {noformat}
> [].bar(1;2;3)
> {noformat}
> h3. Compatibility and performance
>  * No grammar, lexer, or public API changes — valid programs parse as before.
>  * Successful path remains {{BailErrorStrategy}} with no ATN error 
> alternatives.
>  * Extra work is a single O\(n\) token scan only after a recognition failure.



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to