Scott Murphy Heiberg created GROOVY-12694:
---------------------------------------------
Summary: Parser reports Missing ')' at an earlier closed paren
group instead of the actual syntax error
Key: GROOVY-12694
URL: https://issues.apache.org/jira/browse/GROOVY-12694
Project: Groovy
Issue Type: Bug
Components: parser
Affects Versions: 6.0.0
Reporter: Scott Murphy Heiberg
*Environment:* macOS 26.5 (Darwin 25.5.0), JDK 21.0.12 (BellSoft Liberica);
diagnostics taken from {{groovyc --check --error-format=short}}. Also
reproduces on 6.0.1-SNAPSHOT (GROOVY_6_0_X at 7c0dcf17dd) and master
7.0.0-SNAPSHOT (d86ea7eb85).
h3. Description
A Java-style type pattern in a switch case label ({{case Circle c ->}}) is not
Groovy syntax, so the script below must fail to parse. When a record
declaration precedes the switch, however, the error is reported as {{Missing
')'}} inside the record header on line 1 -- which parses fine on its own --
instead of at the offending case label on line 3.
h3. Reproducer
{code:groovy}
record Circle(double radius) {}
Object o = new Circle(1)
String s = switch (o) { case Circle c -> 'circle'; default -> 'other' }
println s
{code}
h3. Actual
{noformat}
record.groovy:1:22: error: Missing ')'
{noformat}
Column 22 is {{radius}} in the record header. Replacing {{case Circle c ->}}
with {{case Circle ->}} makes the file compile, so there is nothing missing on
line 1.
h3. Expected
The diagnostic at the unsupported case label, which is what the same switch
gets when a plain class ({{class Circle { double radius }}}) precedes it
instead of the record:
{noformat}
record.groovy:3:37: error: Unexpected input: 'String s = switch (o) { case
Circle c'
{noformat}
h3. Scope
The record is incidental. Any parenthesised group of the shape {{( <type>
<literal | identifier | (>}} that the parser has already accepted is reported
as a missing {{)}} whenever a syntax error occurs later in the file:
{code:groovy}
def foo(int x) { x }
def y = 1 +
{code}
{noformat}
Missing ')' @ line 1, column 13
{noformat}
Expected {{Unexpected input: '+' @ line 2, column 11}}, which is what the
second line reports on its own. The same happens with {{for (int i = 0; i < 3;
i++) { }}} or {{def y = (Math.max(1, 2))}} on the first line.
h3. Cause
{{org.apache.groovy.parser.antlr4.internal.MissingDelimiterDiagnostic#fromCastPattern}}
(the "unclosed cast" heuristic added for the friendlier missing-delimiter
messages) scans the whole token stream for {{'(' type <expr-start>}} and
reports the earliest match, without checking whether that group's {{)}} was
already matched before the point where the parser failed. A formal parameter
list with a primitive-typed parameter, a classic {{for}} header or a
parenthesised call all match that pattern. Restricting the scan to groups that
are still open at the failure point (the offending token, or the start token of
a failed {{NoViableAltException}} decision) fixes the position while keeping
the real unclosed-cast diagnostics ({{println ((int 123)}} still reports at
{{123}}).
A pull request with regression tests follows.
--
This message was sent by Atlassian Jira
(v10.3.26#10030026)