[
https://issues.apache.org/jira/browse/GROOVY-12353?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
]
Daniel Sun updated GROOVY-12353:
--------------------------------
Description:
h3. Problem
After GROOVY-12169 and GROOVY-12171, several everyday syntax mistakes still
produce a generic *Unexpected input* or *Unexpected character* message that
does not name the actual problem.
Invisible characters (zero-width space, BOM, NUL, form feed) appear as an empty
glyph in quotes. Unclosed quotes and comments are reported as an unexpected
quote or slash rather than as an unclosed literal. {{if true}} without
parentheses, {{x ? y}}, and {{const x = 1}} look like random unexpected tokens
instead of a missing {{(}} / {{:}} or an unimplemented keyword.
h3. Examples (before)
* Source:
{noformat}
println 'Hello
{noformat}
Report:
{noformat}
Unexpected character: ''' @ line 1, column 9.
{noformat}
(caret on the opening quote)
* Source:
{noformat}
/* comment
{noformat}
Report:
{noformat}
Unexpected input: '/'
{noformat}
(no mention of an unclosed comment)
* Source: {{def}} + zero-width space + {{name = null}}
Report:
{noformat}
Unexpected character: ''
{noformat}
(the offending character is invisible)
* Source:
{noformat}
if true { x = 1 }
{noformat}
Report:
{noformat}
Unexpected input: 'true'
{noformat}
* Source:
{noformat}
const x = 1
{noformat}
Report:
{noformat}
Unexpected input: 'const'
{noformat}
* Source:
{noformat}
def n = 1_
{noformat}
Report:
{noformat}
Number ending with underscores is invalid @ line 1, column 10 @ line 1, column
10.
{noformat}
(position duplicated)
* Source:
{noformat}
def m(int... a, int b) {}
{noformat}
Report:
{noformat}
The var-arg parameter strs must be the last parameter
{noformat}
(hard-coded name {{strs}})
h3. Root cause
* Lexer {{UNEXPECTED_CHAR}} inlined the raw character with only a
quote-escape, so control / format characters vanish in the message. An
unexpected quote is almost always an unclosed string, but the message never
said so.
* Unclosed block comments failed the comment rule and were retokenised as
{{/}}, so the parser saw an unexpected slash.
* Parser fallback wording was still ANTLR's *Unexpected input*, even when the
expected set was a single punctuation token (open paren, colon, {{>}}) or the
offending token was a reserved keyword ({{const}}, {{goto}}, {{else}},
{{catch}}, {{finally}}, {{case}}) or EOF.
* Lexer {{require(..., true)}} appended {{@ line N, column M}} to
{{GroovySyntaxError}}, and {{SyntaxException}} appended the same location again.
* {{AstBuilder}} used a hard-coded parameter name {{strs}} in the
varargs-not-last diagnostic.
Grammar-level parser error alternatives are not an option: GROOVY-9588 showed
they enlarge the ATN and slow successful parses.
h3. Goal
Give javac-aligned, developer-facing sentences for these common mistakes, with
an accurate caret, without reintroducing parser error alternatives on the hot
path.
h3. Approach
Error-path-only, two layers:
* Lexer ({{GroovyLexer.g4}} / {{AbstractLexer}}): unexpected quote becomes
*Unclosed string literal*; unclosed block comment becomes *Unclosed comment* at
the opener (same-rule EOF alternative, not a second lexer rule); other
unexpected characters via {{getCharErrorDisplay}} (shared with the GString
{{$}} path from GROOVY-12171). Stop attaching position text on lexer
{{require}} calls so {{SyntaxException}} is the only source of {{@ line N,
column M}}.
* Parser ({{AbstractFriendlyErrorStrategy}}): {{MissingDelimiterDiagnostic}}
still relocates the caret for a missing closer (GROOVY-12169). Everything else
only refines the fallback sentence and keeps ANTLR's offending token:
reserved/misplaced keyword, then a singleton expected punctuation token
({{Missing '('}}, {{Missing ':'}}, {{Missing '>'}}, ...), then *Unexpected end
of input* for EOF.
{{AstBuilder}} reports the actual varargs parameter name.
h3. Expected result (after)
* unclosed single-quoted string becomes *Unclosed string literal*
* unclosed block comment becomes *Unclosed comment* (caret on the opener)
* zero-width space in an identifier becomes {{Unexpected character: '\u200b'}}
* {{if true}} without parentheses becomes {{Missing '('}}
* {{x ? y}} becomes {{Missing ':'}}
* a generic type missing {{>}} before {{(}} becomes {{Missing '>'}}
* {{const x = 1}} becomes {{'const' is not supported; use 'val' or 'static
final' instead}}
* {{goto label}} becomes {{'goto' is not supported}}
* stray {{else}} / {{catch}} / {{case}} become {{'else' without 'if'}} /
{{'catch' without 'try'}} / {{'case' outside of switch}}
* {{throw}} at EOF becomes *Unexpected end of input*
* {{def n = 1_}} becomes *Number ending with underscores is invalid* (position
once)
* {{def m(int... a, int b)}} with a later parameter becomes {{The var-arg
parameter a must be the last parameter}}
Valid programs are unchanged. Successful parses never enter these helpers.
h3. Related
GROOVY-12169, GROOVY-12171, GROOVY-9588, GROOVY-10146
was:
h3. Problem
After GROOVY-12169 and GROOVY-12171, several everyday syntax mistakes still
produce a generic *Unexpected input* or *Unexpected character* message that
does not name the actual problem.
Invisible characters (zero-width space, BOM, NUL, form feed) appear as an empty
glyph in quotes. Unclosed quotes and comments are reported as an unexpected
quote or slash rather than as an unclosed literal. {{if true}} without
parentheses, {{x ? y}}, and {{const x = 1}} look like random unexpected tokens
instead of a missing {{(}} / {{:}} or an unimplemented keyword.
h3. Examples (before)
* Source:
{noformat}
println 'Hello
{noformat}
Report:
{noformat}
Unexpected character: ''' @ line 1, column 9.
{noformat}
(caret on the opening quote)
* Source:
{noformat}
/* comment
{noformat}
Report:
{noformat}
Unexpected input: '/'
{noformat}
(no mention of an unclosed comment)
* Source: {{def}} + zero-width space + {{name = null}}
Report:
{noformat}
Unexpected character: ''
{noformat}
(the offending character is invisible)
* Source:
{noformat}
if true { x = 1 }
{noformat}
Report:
{noformat}
Unexpected input: 'true'
{noformat}
* Source:
{noformat}
const x = 1
{noformat}
Report:
{noformat}
Unexpected input: 'const'
{noformat}
* Source:
{noformat}
def n = 1_
{noformat}
Report:
{noformat}
Number ending with underscores is invalid @ line 1, column 10 @ line 1, column
10.
{noformat}
(position duplicated)
* Source:
{noformat}
def m(int... a, int b) {}
{noformat}
Report:
{noformat}
The var-arg parameter strs must be the last parameter
{noformat}
(hard-coded name {{strs}})
h3. Root cause
* Lexer {{UNEXPECTED_CHAR}} inlined the raw character with only a
quote-escape, so control / format characters vanish in the message. An
unexpected quote is almost always an unclosed string, but the message never
said so.
* Unclosed block comments failed the comment rule and were retokenised as
{{/}}, so the parser saw an unexpected slash.
* Parser fallback wording was still ANTLR's *Unexpected input*, even when the
expected set was a single punctuation token (open paren, colon, {{>}}) or the
offending token was a reserved keyword ({{const}}, {{goto}}, {{else}},
{{catch}}, {{finally}}, {{case}}) or EOF.
* Lexer {{require(..., true)}} appended {{@ line N, column M}} to
{{GroovySyntaxError}}, and {{SyntaxException}} appended the same location again.
* {{AstBuilder}} used a hard-coded parameter name {{strs}} in the
varargs-not-last diagnostic.
Grammar-level parser error alternatives are not an option: GROOVY-9588 showed
they enlarge the ATN and slow successful parses.
h3. Goal
Give javac-aligned, developer-facing sentences for these common mistakes, with
an accurate caret, without reintroducing parser error alternatives on the hot
path.
h3. Approach
Error-path-only, two layers:
* Lexer ({{GroovyLexer.g4}} / {{AbstractLexer}}): unexpected quote becomes
*Unclosed string literal*; unclosed block comment becomes *Unclosed comment* at
the opener (same-rule EOF alternative, not a second lexer rule); other
unexpected characters via {{getCharErrorDisplay}} (shared with the GString
{{$}} path from GROOVY-12171). Stop attaching position text on lexer
{{require}} calls so {{SyntaxException}} is the only source of {{@ line N,
column M}}.
* Parser ({{AbstractFriendlyErrorStrategy}}): {{MissingDelimiterDiagnostic}}
still relocates the caret for a missing closer (GROOVY-12169). Everything else
only refines the fallback sentence and keeps ANTLR's offending token:
reserved/misplaced keyword, then a singleton expected punctuation token
({{Missing '('}}, {{Missing ':'}}, {{Missing '>'}}, ...), then *Unexpected end
of input* for EOF.
{{AstBuilder}} reports the actual varargs parameter name.
h3. Expected result (after)
* unclosed single-quoted string becomes *Unclosed string literal*
* unclosed block comment becomes *Unclosed comment* (caret on the opener)
* zero-width space in an identifier becomes {{Unexpected character: '\u200b'}}
* {{if true}} without parentheses becomes {{Missing '('}}
* {{x ? y}} becomes {{Missing ':'}}
* a generic type missing {{>}} before {{(}} becomes {{Missing '>'}}
* {{const x = 1}} becomes {{'const' is not supported; use 'static final'
instead}}
* {{goto label}} becomes {{'goto' is not supported}}
* stray {{else}} / {{catch}} / {{case}} become {{'else' without 'if'}} /
{{'catch' without 'try'}} / {{'case' outside of switch}}
* {{throw}} at EOF becomes *Unexpected end of input*
* {{def n = 1_}} becomes *Number ending with underscores is invalid* (position
once)
* {{def m(int... a, int b)}} with a later parameter becomes {{The var-arg
parameter a must be the last parameter}}
Valid programs are unchanged. Successful parses never enter these helpers.
h3. Related
GROOVY-12169, GROOVY-12171, GROOVY-9588, GROOVY-10146
> Improve remaining common syntax error messages (unclosed literals, unexpected
> characters, missing punctuation, reserved keywords)
> ---------------------------------------------------------------------------------------------------------------------------------
>
> Key: GROOVY-12353
> URL: https://issues.apache.org/jira/browse/GROOVY-12353
> Project: Groovy
> Issue Type: Improvement
> Reporter: Daniel Sun
> Priority: Major
>
> h3. Problem
> After GROOVY-12169 and GROOVY-12171, several everyday syntax mistakes still
> produce a generic *Unexpected input* or *Unexpected character* message that
> does not name the actual problem.
> Invisible characters (zero-width space, BOM, NUL, form feed) appear as an
> empty glyph in quotes. Unclosed quotes and comments are reported as an
> unexpected quote or slash rather than as an unclosed literal. {{if true}}
> without parentheses, {{x ? y}}, and {{const x = 1}} look like random
> unexpected tokens instead of a missing {{(}} / {{:}} or an unimplemented
> keyword.
> h3. Examples (before)
> * Source:
> {noformat}
> println 'Hello
> {noformat}
> Report:
> {noformat}
> Unexpected character: ''' @ line 1, column 9.
> {noformat}
> (caret on the opening quote)
> * Source:
> {noformat}
> /* comment
> {noformat}
> Report:
> {noformat}
> Unexpected input: '/'
> {noformat}
> (no mention of an unclosed comment)
> * Source: {{def}} + zero-width space + {{name = null}}
> Report:
> {noformat}
> Unexpected character: ''
> {noformat}
> (the offending character is invisible)
> * Source:
> {noformat}
> if true { x = 1 }
> {noformat}
> Report:
> {noformat}
> Unexpected input: 'true'
> {noformat}
> * Source:
> {noformat}
> const x = 1
> {noformat}
> Report:
> {noformat}
> Unexpected input: 'const'
> {noformat}
> * Source:
> {noformat}
> def n = 1_
> {noformat}
> Report:
> {noformat}
> Number ending with underscores is invalid @ line 1, column 10 @ line 1,
> column 10.
> {noformat}
> (position duplicated)
> * Source:
> {noformat}
> def m(int... a, int b) {}
> {noformat}
> Report:
> {noformat}
> The var-arg parameter strs must be the last parameter
> {noformat}
> (hard-coded name {{strs}})
> h3. Root cause
> * Lexer {{UNEXPECTED_CHAR}} inlined the raw character with only a
> quote-escape, so control / format characters vanish in the message. An
> unexpected quote is almost always an unclosed string, but the message never
> said so.
> * Unclosed block comments failed the comment rule and were retokenised as
> {{/}}, so the parser saw an unexpected slash.
> * Parser fallback wording was still ANTLR's *Unexpected input*, even when
> the expected set was a single punctuation token (open paren, colon, {{>}}) or
> the offending token was a reserved keyword ({{const}}, {{goto}}, {{else}},
> {{catch}}, {{finally}}, {{case}}) or EOF.
> * Lexer {{require(..., true)}} appended {{@ line N, column M}} to
> {{GroovySyntaxError}}, and {{SyntaxException}} appended the same location
> again.
> * {{AstBuilder}} used a hard-coded parameter name {{strs}} in the
> varargs-not-last diagnostic.
> Grammar-level parser error alternatives are not an option: GROOVY-9588 showed
> they enlarge the ATN and slow successful parses.
> h3. Goal
> Give javac-aligned, developer-facing sentences for these common mistakes,
> with an accurate caret, without reintroducing parser error alternatives on
> the hot path.
> h3. Approach
> Error-path-only, two layers:
> * Lexer ({{GroovyLexer.g4}} / {{AbstractLexer}}): unexpected quote becomes
> *Unclosed string literal*; unclosed block comment becomes *Unclosed comment*
> at the opener (same-rule EOF alternative, not a second lexer rule); other
> unexpected characters via {{getCharErrorDisplay}} (shared with the GString
> {{$}} path from GROOVY-12171). Stop attaching position text on lexer
> {{require}} calls so {{SyntaxException}} is the only source of {{@ line N,
> column M}}.
> * Parser ({{AbstractFriendlyErrorStrategy}}): {{MissingDelimiterDiagnostic}}
> still relocates the caret for a missing closer (GROOVY-12169). Everything
> else only refines the fallback sentence and keeps ANTLR's offending token:
> reserved/misplaced keyword, then a singleton expected punctuation token
> ({{Missing '('}}, {{Missing ':'}}, {{Missing '>'}}, ...), then *Unexpected
> end of input* for EOF.
> {{AstBuilder}} reports the actual varargs parameter name.
> h3. Expected result (after)
> * unclosed single-quoted string becomes *Unclosed string literal*
> * unclosed block comment becomes *Unclosed comment* (caret on the opener)
> * zero-width space in an identifier becomes {{Unexpected character:
> '\u200b'}}
> * {{if true}} without parentheses becomes {{Missing '('}}
> * {{x ? y}} becomes {{Missing ':'}}
> * a generic type missing {{>}} before {{(}} becomes {{Missing '>'}}
> * {{const x = 1}} becomes {{'const' is not supported; use 'val' or 'static
> final' instead}}
> * {{goto label}} becomes {{'goto' is not supported}}
> * stray {{else}} / {{catch}} / {{case}} become {{'else' without 'if'}} /
> {{'catch' without 'try'}} / {{'case' outside of switch}}
> * {{throw}} at EOF becomes *Unexpected end of input*
> * {{def n = 1_}} becomes *Number ending with underscores is invalid*
> (position once)
> * {{def m(int... a, int b)}} with a later parameter becomes {{The var-arg
> parameter a must be the last parameter}}
> Valid programs are unchanged. Successful parses never enter these helpers.
> h3. Related
> GROOVY-12169, GROOVY-12171, GROOVY-9588, GROOVY-10146
--
This message was sent by Atlassian Jira
(v8.20.10#820010)