Copilot commented on code in PR #2877:
URL: https://github.com/apache/groovy/pull/2877#discussion_r3937641155


##########
src/main/java/org/apache/groovy/parser/antlr4/GroovySyntaxError.java:
##########
@@ -19,7 +19,14 @@
 package org.apache.groovy.parser.antlr4;
 
 /**
- * Represents a syntax error of groovy program
+ * Represents a syntax error of a Groovy program, raised by the lexer or 
parser.
+ * <p>
+ * The message is the diagnostic as produced by the recogniser (for example
+ * {@code Unclosed string literal} or {@code Unexpected character: '\u200b'}).

Review Comment:
   Java translates Unicode escapes before parsing comments, so this Javadoc 
example renders an actual invisible U+200B rather than the literal `\u200b` 
emitted by the diagnostic. Escape the backslash through `\u005c` so the 
generated API documentation shows the intended text.



##########
src/main/java/org/apache/groovy/parser/antlr4/internal/AbstractFriendlyErrorStrategy.java:
##########
@@ -49,24 +71,107 @@ abstract class AbstractFriendlyErrorStrategy extends 
DefaultErrorStrategy {
     }
 
     /**
-     * Prefer a precise "Missing …" delimiter diagnostic when the token stream
-     * clearly indicates an unclosed / incomplete construct; otherwise fall
-     * back to the generic message.
+     * Prefer a relocated "Missing …" closer when the token stream supports it;
+     * otherwise refine the generic {@code Unexpected input: ...} fallback
+     * (reserved keyword, sole expected punctuation, unexpected EOF).
      */
     private void reportFriendlyError(final Parser recognizer, final 
RecognitionException e, final String fallbackMessage) {
-        MissingDelimiterDiagnostic.Hit hit = null;
         try {
-            // Incomplete / synthetic contexts can leave token indices out of 
range.
-            hit = 
MissingDelimiterDiagnostic.locate(recognizer.getInputStream(), e);
+            // Incomplete / synthetic contexts can leave token indices out of 
range,
+            // and getExpectedTokens() can reject an invalid ATN state number.
+            MissingDelimiterDiagnostic.Hit hit = 
MissingDelimiterDiagnostic.locate(recognizer.getInputStream(), e);
+            if (hit != null) {
+                recognizer.notifyErrorListeners(hit.at, hit.message, e);
+                return;
+            }
+            notifyErrorListeners(recognizer, refineFallbackMessage(e, 
fallbackMessage), e);
         } catch (IndexOutOfBoundsException | IllegalArgumentException ignored) 
{
-            // Fall through to the generic message. Catch only locate()'s known
-            // defensive failures — never listener-side fatals (e.g. 
addFatalError).
+            notifyErrorListeners(recognizer, fallbackMessage, e);

Review Comment:
   The guarded block now includes both listener-dispatch calls. If an error 
listener throws `IllegalArgumentException` or `IndexOutOfBoundsException`, that 
exception is mistaken for a diagnostic lookup failure and the listener is 
invoked a second time with the fallback message, masking the original failure 
and potentially duplicating side effects. Keep only `locate`/message refinement 
inside the defensive catch and dispatch after it.



##########
src/antlr/GroovyLexer.g4:
##########
@@ -997,9 +997,17 @@ WS  : ([ \t]+ | LineEscape+) -> skip
 NL  : LineTerminator   { ignoreTokenInsideParens(); }
     ;
 
-// Multiple-line comments (including groovydoc comments)
+// Multiple-line comments (including groovydoc comments).
+// The EOF alternative is an error-path-only match: a well-formed comment
+// takes the first alt and never reaches it (unlike parser error alternatives,
+// which GROOVY-9588 showed can pollute prediction). javac reports
+// "unclosed comment" at the opener; requireUnclosedComment keeps the caret 
there.
+// Type is set in an action rather than `-> type(NL)` so the rule may have
+// more than one outermost alternative (ANTLR requires `->` commands to be
+// last on a single outermost alt).
 ML_COMMENT
-    :   '/*' .*? '*/'       { addComment(0); 
ignoreMultiLineCommentConditionally(); } -> type(NL)
+    :   '/*' .*? '*/'       { addComment(0); 
ignoreMultiLineCommentConditionally(); setType(NL); }
+    |   '/*' .*? EOF        { requireUnclosedComment(errorIgnored); 
addComment(0); setType(NL); }

Review Comment:
   ANTLR lexers prefer the alternative that consumes the most input. When a 
valid `/* ... */` is followed by more source, the new EOF alternative can 
consume the closing delimiter and everything through EOF, so it wins over the 
shorter first alternative, reports a valid comment as unclosed, and swallows 
the remaining source. Put the `*/`/EOF choice after one shared non-greedy loop 
so `*/` terminates the token as soon as it is encountered.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to