paulk-asert commented on PR #2924:
URL: https://github.com/apache/groovy/pull/2924#issuecomment-5662336534

   AI read:
   
   > I went through this in some detail, including building the branch merged 
with current master. Summary: good work, and the parts that looked risky turn 
out to be safe. There is one defect I think should be fixed before merge.
   > 
   > ## The blocking one: `unmatchedTypeArgument` misfires on ordinary 
comparisons
   > 
   > The helper decides "missing `>`" from the *absence* of a closing `>` in a 
token window. A comparison never has one, so any comparison whose left operand 
starts with a capital letter gets rewritten when an unrelated syntax error 
follows:
   > 
   > | Source | master | this PR |
   > |---|---|---|
   > | `int MAX = 10`<br>`if (MAX < x) { println 1 +` | `Unexpected input: '{ 
println 1 +\n}'` | **`Missing '>'`** |
   > | `if (Integer.MAX_VALUE < x) { println 1 +` | `Unexpected input: '{ 
println 1 +\n}'` | **`Missing '>'`** |
   > | `def X = 1`<br>`def y = X << 2 +` | `Unexpected input: '+'` | **`Missing 
'>'`** |
   > | `int max = 10`<br>`if (max < x) { println 1 +` | `Unexpected input: ...` 
| `Unexpected '}'` — declines correctly |
   > 
   > Uppercase-initial receivers are common in exactly this position: constants 
(`MAX`, `LIMIT`, `SIZE`) and class references. The old message was unhelpful, 
but the new one is confidently wrong and points the reader at generics that are 
not there, which I think is a step back.
   > 
   > Two things make it harder to reason about than it first appears:
   > 
   > - **Distance does not save it.** With six statements between the 
comparison and the error, the message still changes to `Missing '>'`.
   > - **An unrelated later token cancels it.** Adding `&& 4 > 5` to the 
condition closes the imagined type argument and the message reverts. So for the 
same class of mistake the advice varies for reasons the author cannot see.
   > 
   > ### The `<<` case is an off-by-one in the guard written for it
   > 
   > ```java
   > if (type == LT) {
   >     // `<<` is two LT tokens (shift), not nested type arguments.
   >     if (prev != null && prev.getType() == LT) {
   >         prev = t;
   >         continue;                       // the second '<' declines ...
   >     }
   >     if (prev != null && prev.getType() == CapitalizedIdentifier) {
   >         depth++;                        // ... but the first already 
counted
   >         typeOpener = true;
   >     }
   > ```
   > 
   > By the time the second `<` is seen and skipped, the first has already 
raised `depth` and set `typeOpener`, so the method still returns `Missing '>'`. 
The comment describes the intended behaviour; the code does not implement it. 
Deciding on lookahead — if the next default-channel token is also `LT`, treat 
the pair as a shift and skip both — would match the comment. `>>` is unaffected 
because `depth` is zero there.
   > 
   > ### Suggested narrowing
   > 
   > Require positive evidence of a type-argument list rather than mere absence 
of `>`: for example that the token after the opening `<` is itself a type name 
and the construct sits in a declaration position, or that the offending token 
is the identifier immediately following the would-be type argument (the 
`List<Integer name` shape that motivated the helper). The genuine cases in the 
PR's own tests all have that shape; the false positives do not. Worth adding 
the three inputs above to `ErrorStrategyTest`.
   > 
   > The separate `AstBuilder` path is sound, for what it's worth — it fires 
only when *both* operands look like type names (`A < B foo`), and only inside a 
branch that was already going to throw.
   > 
   > ## What I checked that came out clean
   > 
   > **No new rejections.** The diff adds 23 `createParsingFailedException` 
calls. Twenty-one replace an existing call one for one. The two genuine 
additions are both guarded by a condition that already threw:
   > 
   > - `Anonymous classes cannot declare constructors` replaces the more 
general `Invalid method declaration: Foo` in the same branch.
   > - `Missing '>'` in the command-expression path precedes that branch's 
existing `Unexpected input: ...` throw.
   > 
   > So no source that compiled before this PR fails after it. That was my main 
concern with a change of this shape.
   > 
   > **The position change is strictly additive.** `asBoolean(stop)` → 
`hasEndPosition(node)` is the real fix here, and a good one: Groovy truth on an 
`ASTNode` is just non-null, so a synthetic `ConstantExpression` with −1 
coordinates was passing the test and writing −1 over an authored parent's end. 
I dumped every position from 483 files under both builds and diffed:
   > 
   > | Outcome | Nodes |
   > |---|---|
   > | −1 replaced by a real position | 871 |
   > | Real position replaced by −1 | 0 |
   > | Real position moved | 0 |
   > | Unchanged | 256,384 |
   > 
   > The gains are almost all argument lists for empty parentheses, plus the 
for-control. Confirmed directly for the two shapes the JIRA names:
   > 
   > ```
   > for (int i = 0; i < 3; i++) { }
   >   for-control  master: -1,-1 to -1,-1     PR: 4,19 to 4,40
   > 
   > new StringBuilder()
   >   arguments    master: -1,-1 to -1,-1     PR: 5,30 to 5,32
   > ```
   > 
   > One consequence worth stating: `SecureASTCustomizer` tells authored nodes 
from generated ones by `lineNumber > 0`, so nodes that gain a position become 
visible to it. The direction is toward checking more rather than less, and its 
60 tests pass on the merge, but that is the mechanism by which a position 
change can reach behaviour rather than only tooling.
   > 
   > **Merged with current master it is green.** The base is 20 commits behind, 
and master has since changed switch handling in the same `AstBuilder`, so the 
combination is worth exercising. It merges cleanly; 905 tests pass across the 
parser, generics, switch, the four updated bug tests and `SecureASTCustomizer`; 
`lintGate` passes. Please rebase so CI sees that combination too.
   > 
   > **The new diagnostics land as claimed.** Of the twelve shapes the JIRA 
lists, nine improve and the other three are valid Groovy that still parses:
   > 
   > | Input | master | this PR |
   > |---|---|---|
   > | `def a = new int[]` | `Missing '{'` | `Array dimension missing; specify 
a size or add an initializer '{}'` |
   > | `def a = new int[3]{1,2}` | `Unexpected input: '{'` | `Cannot combine an 
array size with an array initializer` |
   > | `List<Integer name = null` | `Unexpected input: 'List<Integer'` | 
`Missing '>'` |
   > | `def m() { import java.util.List; 1 }` | `Unexpected input: 'import'` | 
`'import' is only allowed at the beginning of a compilation unit` |
   > | `do { println 1 } (true)` | `Unexpected end of input` | `Missing 
'while'` |
   > | `do println 1; println 2 while (false)` | `Unexpected input: '1;'` | 
`do-while body must be a single statement; wrap multiple statements in '{ }'` |
   > | `interface I { def m() default {1} }` | `Unexpected input: 'default'` | 
`'default' cannot follow a method header; put 'default' before the method name` 
|
   > | `def c = { -> def say(String m) { } }()` | `Unexpected input: '('` | 
`Method definition not expected here` |
   > | `def m() { 1 } }` and the `)`, `]`, `,` variants | `Unexpected input: 
'}'` | `Unexpected '}'` |
   > 
   > Moving the duplicate-`default` caret from the first branch's body to the 
offending `default` keyword is the right target.
   > 
   > ## Two smaller points
   > 
   > **The hedged generics assertions.** Five cases in `GenericsUsageTest` 
moved to `shouldFailCompilationWithAnyMessage`, which passes if *either* the 
new or the old message appears. Run repeatedly against this branch, four of the 
five produce the new `Missing '>'` every time and the fifth (the `for 
(Iterator<String i = ...)` case) produces the old message every time — nothing 
I could observe varies. An assertion that accepts either will not notice if the 
improvement silently stops working. If the hedge is there because the message 
is known to differ under some configuration, could you note that condition in a 
comment? Otherwise the four deterministic cases could assert the exact message 
and the fifth assert the old one.
   > 
   > **The rewordings are broad and user-visible.** About fifteen long-standing 
messages change text. The new wording is better and closer to javac in each 
case, and nothing in the tree still refers to the old text (I checked). The 
exposure is external: build tools, IDE plugins and user test suites that match 
on message substrings. `Numbers.java` is a runtime class, so its 
`NumberFormatException` text can surface outside compilation. Normal for a 
major release and no change needed in the PR, but it should go in the release 
notes rather than being found by downstream projects.
   > 
   > ## Recommendation
   > 
   > Merge after narrowing `unmatchedTypeArgument`, plus a rebase. Everything 
else is a genuine improvement, and the test coverage the PR adds is substantial 
— `ErrorStrategyTest` goes from 46 to 118 cases, and 
`PositionConfigureUtilsTest` from 2 to 21, including the null, 
`EmptyExpression.INSTANCE`, synthetic-constant and zero-last-line paths.
   


-- 
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