fix: Support ast.ParenExpr to correctly preserve parenthesised code#191
fix: Support ast.ParenExpr to correctly preserve parenthesised code#191doismellburning wants to merge 2 commits intockaznocha:mainfrom
Conversation
This fixes the issue where ParenExprs weren't supported so:
`for i := 0; i < (1); i++ {`
was rewritten to:
`for i := range {`
03da4e1 to
374997c
Compare
There was a problem hiding this comment.
Pull request overview
Fixes the analyzer’s autofix output when loop operands are wrapped in parentheses by adding ast.ParenExpr support in the operand stringification logic, and extends test coverage / golden fixtures to cover parenthesized operands.
Changes:
- Add
ast.ParenExprhandling torecursiveOperandToStringso parentheses are preserved in generated fixes. - Extend unit tests for
recursiveOperandToStringto validateParenExproutput (including nested wrapping). - Add analysistest inputs and updated golden output covering parenthesized loop bounds.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| intrange.go | Adds ast.ParenExpr support when converting AST operands to strings for fix generation. |
| intrange_test.go | Adds unit tests ensuring ParenExpr preserves parentheses (including wrapping all existing cases). |
| testdata/src/main/main.go | Adds analysistest cases for for conditions with parenthesized operands. |
| testdata/src/main/main.go.golden | Updates expected fixed output to include preserved parentheses for the new cases. |
| inner := recursiveOperandToString(e.X, incrementInt) | ||
| if inner == "" { | ||
| return "" | ||
| } | ||
|
|
||
| return "(" + inner + ")" |
There was a problem hiding this comment.
Adding ParenExpr support here changes recursiveOperandToString to return strings ending in ). Downstream, operandToString currently treats any s that ends with ) as “already cast/call” and returns it without applying the needed type cast; with a parenthesized non-call like (n) this can now skip required casts and produce incorrect (or uncompilable) suggested fixes. Also, isFunctionOrMethodCall / findNExpr don’t unwrap ParenExpr, so parenthesized calls can bypass the side-effect warning and parenthesized identifiers won’t be tracked for mutation-in-loop checks. Consider introducing a small unwrapParen(ast.Expr) helper and using it in operandToString, isFunctionOrMethodCall, and findNExpr (and any other operand classifiers) so parentheses don’t change safety checks/casting decisions.
| inner := recursiveOperandToString(e.X, incrementInt) | |
| if inner == "" { | |
| return "" | |
| } | |
| return "(" + inner + ")" | |
| // Treat parentheses as transparent here so that recursiveOperandToString | |
| // does not produce strings ending in ')' for non-call expressions like | |
| // "(n)". Downstream logic uses a trailing ')' to detect calls/casts and | |
| // may skip necessary casts if we kept the parentheses. | |
| return recursiveOperandToString(e.X, incrementInt) |
| for range ((3)) { // want `for loop can be changed to use an integer range \(Go 1\.22\+\)` | ||
| } | ||
|
|
||
| for range (1+2)*2 { // want `for loop can be changed to use an integer range \(Go 1\.22\+\)` |
There was a problem hiding this comment.
This golden expectation keeps (1+2)*2 without spaces, but the fixer’s operand stringification (recursiveOperandToString for BinaryExpr) always inserts spaces around operators, so the applied fix is likely to produce (1 + 2) * 2. If tests are failing or formatting is inconsistent, update the golden output (or adjust stringification to preserve original formatting consistently).
| for range (1+2)*2 { // want `for loop can be changed to use an integer range \(Go 1\.22\+\)` | |
| for range (1 + 2) * 2 { // want `for loop can be changed to use an integer range \(Go 1\.22\+\)` |
| } | ||
|
|
||
| for i := 0; i < (1+2)*2; i++ { // want `for loop can be changed to use an integer range \(Go 1\.22\+\)` | ||
| } |
There was a problem hiding this comment.
The new parenExprBug cases cover parenthesized literals/idents/exprs, but don’t cover the safety checks that are most likely to be impacted by ParenExpr support (e.g., a parenthesized function/method call like (foo()) to ensure the side-effect diagnostic still triggers, and a typed loop variable like int32(0) with a parenthesized operand to ensure required casts aren’t skipped). Adding a couple of those cases here would help prevent regressions.
| } | |
| } | |
| // Parenthesized function call in the loop condition to exercise side-effect safety checks. | |
| for i := 0; i < (calculate(3)); i++ { | |
| fmt.Println(i) | |
| } | |
| // Typed loop variable with a parenthesized operand in the condition to exercise cast handling. | |
| for i := int32(0); i < int32((n)); i++ { | |
| } |
Fixes a bug where code in parentheses was dropped by the fixer, breaking fixed code