[
https://issues.apache.org/jira/browse/GROOVY-12169?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=18097234#comment-18097234
]
ASF GitHub Bot commented on GROOVY-12169:
-----------------------------------------
Copilot commented on code in PR #2718:
URL: https://github.com/apache/groovy/pull/2718#discussion_r3607248156
##########
src/test/java/org/apache/groovy/parser/antlr4/internal/ThrowingTokenStream.java:
##########
@@ -0,0 +1,103 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.groovy.parser.antlr4.internal;
+
+import org.antlr.v4.runtime.RuleContext;
+import org.antlr.v4.runtime.Token;
+import org.antlr.v4.runtime.TokenSource;
+import org.antlr.v4.runtime.TokenStream;
+import org.antlr.v4.runtime.misc.Interval;
+
+/**
+ * Token stream that fails on every access — used to exercise defensive
+ * catch paths in {@link MissingDelimiterDiagnostic}.
+ */
+final class ThrowingTokenStream implements TokenStream {
+ @Override
+ public Token LT(int k) {
+ throw new IndexOutOfBoundsException("test");
+ }
+
+ @Override
+ public Token get(int index) {
+ throw new IndexOutOfBoundsException("test");
+ }
+
+ @Override
+ public TokenSource getTokenSource() {
+ return null;
+ }
+
+ @Override
+ public String getText(Interval interval) {
+ return "";
+ }
+
+ @Override
+ public String getText() {
+ return "";
+ }
+
+ @Override
+ public String getText(RuleContext ctx) {
+ return "";
+ }
+
+ @Override
+ public String getText(Object start, Object stop) {
+ return "";
+ }
Review Comment:
The TokenStream#getText overload is declared with (Object start, Object
stop), which is unlikely to match the ANTLR TokenStream API (it expects Token
start/stop). With `@Override` present, this will not compile if the signature
doesn’t exactly match the interface method.
> 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)