Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

x/tools/gopls: yet more modernizer bugs #71847

Open
1 of 8 tasks
adonovan opened this issue Feb 19, 2025 · 17 comments
Open
1 of 8 tasks

x/tools/gopls: yet more modernizer bugs #71847

adonovan opened this issue Feb 19, 2025 · 17 comments
Labels
BugReport Issues describing a possible bug in the Go implementation. gopls Issues related to the Go language server, gopls. Tools This label describes issues relating to any tools in the x/tools repository.
Milestone

Comments

@adonovan
Copy link
Member

adonovan commented Feb 19, 2025

Found after running modernize on std + cmd:

  • don't apply maps transformations to maps package itself or its in-package tests.
  • don't import slices (or almost any other package) from runtime or its dependencies.
  • remove unused imports such as "sort"
  • for i := 0; i < 1e3; i++ -> for range 1e3 requires an int conversion or change of literal.
  • for i := 0; i < n; i++ -> for range n is invalid if the loop body contains i++ (I thought we already checked this?). Example: path/filepath.scanChunk (Fixed by https://go.dev/cl/650815 )
  • some comments go missing (e.g. in minmax).
  • var i int; for i = 0; i < n; i++ -> for range n removes uses of i that might make local var i unreferenced (example)
  • some comments go missing (e.g. in minmax).

Refactoring is hard. :( We may need to be selective about which categories we apply without review until all these bugs are fixed. Perhaps we also need a flag to select categories too.

Mistakes that break the build are of low severity, because the mistake is impossible to miss and the remedy is obvious. Loss of commentary is of medium severity as it may go undetected due to reviewer fatigue in a large CL. The really severe problems are latent behavior changes such as the "loop body contains i++" case above (now fixed).

@gopherbot gopherbot added Tools This label describes issues relating to any tools in the x/tools repository. gopls Issues related to the Go language server, gopls. labels Feb 19, 2025
@gopherbot gopherbot added this to the Unreleased milestone Feb 19, 2025
@gabyhelp
Copy link

Related Issues

(Emoji vote if this was helpful or unhelpful; more detailed feedback welcome in this discussion.)

@gabyhelp gabyhelp added the FeatureRequest Issues asking for a new feature that does not need a proposal. label Feb 19, 2025
@adonovan adonovan added BugReport Issues describing a possible bug in the Go implementation. and removed FeatureRequest Issues asking for a new feature that does not need a proposal. labels Feb 19, 2025
@gopherbot
Copy link
Contributor

Change https://go.dev/cl/650815 mentions this issue: gopls/internal/analysis/modernize: fix rangeint bug

gopherbot pushed a commit to golang/tools that referenced this issue Feb 19, 2025
info.Defs[v] is nil if the loop variable is not declared
(for i = 0 instead of for i := 0).

+ test

Updates golang/go#71847

Change-Id: I28f82188e813f2d4f1ddc9335f0c13bd90c31ec1
Reviewed-on: https://go-review.googlesource.com/c/tools/+/650815
Auto-Submit: Alan Donovan <adonovan@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Robert Findley <rfindley@google.com>
@findleyr findleyr modified the milestones: Unreleased, gopls/v0.18.0 Feb 20, 2025
@findleyr
Copy link
Member

Putting this in the v0.18.0 milestone until we make a decision. It seems premature to expose a tool that blanket-applies these modernizers.

@gopherbot
Copy link
Contributor

Change https://go.dev/cl/651095 mentions this issue: [gopls-release-branch.0.18] gopls/internal/analysis/modernize: fix rangeint bug

@findleyr findleyr modified the milestones: gopls/v0.18.0, gopls/v0.19.0 Feb 20, 2025
gopherbot pushed a commit to golang/tools that referenced this issue Feb 20, 2025
…ngeint bug

info.Defs[v] is nil if the loop variable is not declared
(for i = 0 instead of for i := 0).

+ test

Updates golang/go#71847

Change-Id: I28f82188e813f2d4f1ddc9335f0c13bd90c31ec1
Reviewed-on: https://go-review.googlesource.com/c/tools/+/650815
Auto-Submit: Alan Donovan <adonovan@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Robert Findley <rfindley@google.com>
(cherry picked from commit 300465c)
Reviewed-on: https://go-review.googlesource.com/c/tools/+/651095
Auto-Submit: Robert Findley <rfindley@google.com>
Reviewed-by: Alan Donovan <adonovan@google.com>
Commit-Queue: Alan Donovan <adonovan@google.com>
@erikdubbelboer
Copy link
Contributor

Here is another buggy transformation it did on my code:

-       start := strings.Index(origin, "//")
-       if start < 0 {
-               start = 0
-       } else {
-               start += 2
-       }
+       start := max(strings.Index(origin, "//"), 0)

@adonovan
Copy link
Member Author

Many thanks for the bug report. It appears that minmax failed to check that IfStmt.Else is nil in its matcher for pattern 2. Fix pending.

@gopherbot
Copy link
Contributor

Change https://go.dev/cl/651375 mentions this issue: gopls/internal/analysis/modernize: fix minmax bug

gopherbot pushed a commit to golang/tools that referenced this issue Feb 21, 2025
The matcher for pattern 2 forgot to check that the IfStmt.Else
subtree was nil, leading to unsound fixes.

Updates golang/go#71847

Change-Id: I0919076c1af38012cedf3072ef5d1117e96a64b9
Reviewed-on: https://go-review.googlesource.com/c/tools/+/651375
Reviewed-by: Jonathan Amsterdam <jba@google.com>
Auto-Submit: Alan Donovan <adonovan@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
@nemith
Copy link
Contributor

nemith commented Feb 21, 2025

I added #71878 but it seems to be the same bug that @erikdubbelboer reported (I checked the OP here and not the comments first. Sorry)

@adonovan
Copy link
Member Author

I added #71878 but it seems to be the same bug that @erikdubbelboer reported (I checked the OP here and not the comments first. Sorry)

Don't apologize, filing a separate issue is much appreciated. Let's make that issue the canonical one for this bug.

@gopherbot
Copy link
Contributor

Change https://go.dev/cl/651237 mentions this issue: [gopls-release-branch.0.18] gopls/internal/analysis/modernize: fix minmax bug

gopherbot pushed a commit to golang/tools that referenced this issue Feb 21, 2025
…nmax bug

The matcher for pattern 2 forgot to check that the IfStmt.Else
subtree was nil, leading to unsound fixes.

Updates golang/go#71847

Change-Id: I0919076c1af38012cedf3072ef5d1117e96a64b9
Reviewed-on: https://go-review.googlesource.com/c/tools/+/651375
Reviewed-by: Jonathan Amsterdam <jba@google.com>
Auto-Submit: Alan Donovan <adonovan@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
(cherry picked from commit 96bfb60)
Reviewed-on: https://go-review.googlesource.com/c/tools/+/651237
Reviewed-by: Robert Findley <rfindley@google.com>
@findleyr findleyr modified the milestones: gopls/v0.19.0, gopls/v0.18.1 Feb 21, 2025
@seehuhn
Copy link

seehuhn commented Feb 23, 2025

Here is a buggy transformation by "modernize":

 func Test(a []float64) float64 {
-       x := 0.0
-       if x < a[0] {
-               x = a[0]
-       } else if x > a[1] {
-               x = a[1]
-       }
+       x := max(0.0, a[0])
        return x
 }

Before the change, Test([]float64{-1.0, -0.5}) returns -0.5, after applying "modernize" the same call returns 0.

@adonovan
Copy link
Member Author

Here is a buggy transformation by "modernize":

Thanks; this is the same bug as above (and thus #71878), which is now fixed at head. We'll release the patch today.

@dethi
Copy link

dethi commented Feb 25, 2025

First, thanks a lot for the tool! I'm slowly applying on our codebase and it works super well.

I found one additional bug in the transformation of i := 0; for i = 0; i < n; i++ -> for i = range n if i is used outside of the for loop body. See the minimal reproduction: https://go.dev/play/p/9tlB2oilyJn

@adonovan
Copy link
Member Author

First, thanks a lot for the tool! I'm slowly applying on our codebase and it works super well.

I found one additional bug in the transformation of i := 0; for i = 0; i < n; i++ -> for i = range n if i is used outside of the for loop body. See the minimal reproduction: https://go.dev/play/p/9tlB2oilyJn

Good point, thanks for reporting it. I've split this out as #71952.

Glad it (mostly) works for you.

@adonovan
Copy link
Member Author

First, thanks a lot for the tool! I'm slowly applying on our codebase

Reminder: use this command to batch-apply fixes:

$ go run golang.org/x/tools/gopls/internal/analysis/modernize/cmd/modernize@latest -fix -test ./...

(Unfortunately the vital -fix flag was missing from the original release notes.)

@dethi
Copy link

dethi commented Feb 25, 2025

Yes, that's what I'm using. I'm doing it package by package, to get smaller diff and avoid merge conflict in our fast changing monorepo.

My complete process is actually, for each packages:

  • 1st change: replace interface{} -> any, using gofmt -r 'interface{} -> any' -w. Mostly because this is an always safe/uninteresting change. Isolating it make it easier to review the later changes
  • 2nd change: modernize -fix -test=false
  • 3rd change: modernize -fix -test=true, splitting out the test in a separate change gives us more confidence

@adonovan
Copy link
Member Author

Yes, that's what I'm using. I'm doing it package by package, to get smaller diff and avoid merge conflict in our fast changing monorepo.

My complete process is actually, for each packages:

  • 1st change: replace interface{} -> any, using gofmt -r 'interface{} -> any' -w. Mostly because this is an always safe/uninteresting change. Isolating it make it easier to review the later changes
  • 2nd change: modernize -fix -test=false
  • 3rd change: modernize -fix -test=true, splitting out the test in a separate change gives us more confidence

That's a good idea. Others have pointed out the any fixes are so numerous that they need to be done as one large but low-risk change; perhaps we should have better flag support for selecting categories of diagnostics. Separating the tests is a nice touch. Do let us know if there are things we could do to make this workflow easier.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
BugReport Issues describing a possible bug in the Go implementation. gopls Issues related to the Go language server, gopls. Tools This label describes issues relating to any tools in the x/tools repository.
Projects
None yet
Development

No branches or pull requests

8 participants