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: staticcheck QF1003 quickfix produces corrupted textedit #63930

Open
tttoad opened this issue Nov 3, 2023 · 17 comments
Open

x/tools/gopls: staticcheck QF1003 quickfix produces corrupted textedit #63930

tttoad opened this issue Nov 3, 2023 · 17 comments
Labels
gopls Issues related to the Go language server, gopls. NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one. Tools This label describes issues relating to any tools in the x/tools repository.
Milestone

Comments

@tttoad
Copy link

tttoad commented Nov 3, 2023

gopls version

Build info

golang.org/x/tools/gopls v0.14.1
golang.org/x/tools/gopls@v0.14.1 h1:XaTETpi7Q67XO8nftquJitcx+9c2bPclO8Kz2sBVvec=
github.com/BurntSushi/toml@v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak=
github.com/google/go-cmp@v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/sergi/go-diff@v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
golang.org/x/exp/typeparams@v0.0.0-20221212164502-fae10dda9338 h1:2O2DON6y3XMJiQRAS1UWU+54aec2uopH3x7MAiqGW
6Y=
golang.org/x/mod@v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY=
golang.org/x/sync@v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ=
golang.org/x/sys@v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
golang.org/x/telemetry@v0.0.0-20231011160506-788d5629a052 h1:1baVNneD/IRxmu8JQdBuki78zUqBtZxq8smZXQj0X2Y=
golang.org/x/text@v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
golang.org/x/tools@v0.14.1-0.20231026192422-8b5abd452b28 h1:5YgdZAe2w0x3Xrjv0+GXrI0jvm7qCQK/ySGFfiEHMfU=
golang.org/x/vuln@v1.0.1 h1:KUas02EjQK5LTuIx1OylBQdKKZ9jeugs+HiqO5HormU=
honnef.co/go/tools@v0.4.5 h1:YGD4H+SuIOOqsyoLOpZDWcieM28W47/zRO7f+9V3nvo=
mvdan.cc/gofumpt@v0.4.0 h1:JVf4NN1mIpHogBj7ABpgOyZc65/UUOkKQFkoURsz4MM=
mvdan.cc/xurls/v2@v2.4.0 h1:tzxjVAj+wSBmDcF6zBB7/myTy3gX9xvi8Tyr28AuQgc=
go: go1.20.7

go env

GO111MODULE=""
GOARCH="arm64"
GOBIN=""
GOCACHE="/Users/toad/Library/Caches/go-build"
GOENV="/Users/toad/Library/Application Support/go/env"
GOEXE=""
GOEXPERIMENT=""
GOFLAGS=""
GOHOSTARCH="arm64"
GOHOSTOS="darwin"
GOINSECURE=""
GOMODCACHE="/Users/toad/work/pkg/mod"
GONOPROXY=""
GONOSUMDB=""
GOOS="darwin"
GOPATH="/Users/toad/work"
GOPRIVATE=""
GOPROXY="https://goproxy.io,direct"
GOROOT="/Users/toad/go/go1.20.7"
GOSUMDB="sum.golang.org"
GOTMPDIR=""
GOTOOLDIR="/Users/toad/go/go1.20.7/pkg/tool/darwin_arm64"
GOVCS=""
GOVERSION="go1.20.7"
GCCGO="gccgo"
AR="ar"
CC="clang"
CXX="clang++"
CGO_ENABLED="1"
GOMOD="/Users/toad/work/demo5/go.mod"
GOWORK=""
CGO_CFLAGS="-O2 -g"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-O2 -g"
CGO_FFLAGS="-O2 -g"
CGO_LDFLAGS="-O2 -g"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -arch arm64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-pre
fix-map=/var/folders/g1/tgmnlrdn3vxgv08kdgh9vpkw0000gn/T/go-build1758453573=/tmp/go-build -gno-record-gcc-switc
hes -fno-common"

What did you do?

2023-11-03.18.33.31.mov

What did you expect to see?

2023-11-03.18.18.41.mov

I found the problem on "honnef.co/go/tools" and I will commit PR to that repository for fixing the problem.

@tttoad tttoad added gopls Issues related to the Go language server, gopls. Tools This label describes issues relating to any tools in the x/tools repository. labels Nov 3, 2023
@gopherbot gopherbot added this to the Unreleased milestone Nov 3, 2023
@hyangah
Copy link
Contributor

hyangah commented Nov 3, 2023

Thanks for the report. This functionality comes from staticcheck. Can you please open an issue in the staticcheck issue tracker, and once fixed, we can update gopls's dependency. cc @dominikh

@hyangah hyangah modified the milestones: Unreleased, gopls/backlog Nov 3, 2023
@hyangah hyangah changed the title x/tools/gopls: IfElseToSwitch Results not expected x/tools/gopls: staticcheck QF1003 quickfix produces corrupted textedit Nov 3, 2023
@dominikh
Copy link
Member

dominikh commented Nov 6, 2023

@hyangah Are we sure this isn't an issue with how gopls applies edits?

For the code in the OP, Staticcheck emits 6 code edits. The first 5 replace if <cond> { with case <cond>: and delete the closing } of the block. The sixth edit inserts switch <x> {\n in front of the first if keyword. I admit that this order of edits is non-trivial, but IMO it should work fine, assuming correct tracking of how positions change due to insertions and deletions.

The go/analysis docs do say that

Diagnostics should not contain SuggestedFixes that overlap.

but I'm not sure that replace 0..10 with text and insert text at 0 can be considered to be overlapping.

@hyangah hyangah removed this from the gopls/backlog milestone Nov 6, 2023
@hyangah
Copy link
Contributor

hyangah commented Nov 6, 2023

Thanks @dominikh

@findleyr @adonovan Can you please take a look whether the quick fix translation gopls is doing is sane?
EDIT: I could reproduce it I cannot reproduce it any more. :-(

@tttoad You said you already found an issue in honnef.co/go/tools. Can you share the PR you have?

package foo

import "fmt"

func fn() {
	x := 0
	if x == 1 || x == 2 {
	} else if x == 3 {
		fmt.Println(x)
	} else {
		fmt.Println(x)
	}
}

@hyangah hyangah added the NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one. label Nov 6, 2023
@adonovan adonovan self-assigned this Nov 6, 2023
@dominikh
Copy link
Member

dominikh commented Nov 6, 2023

@hyangah the proposed PR is dominikh/go-tools#1466 — it merges the first and last edit into one.

@findleyr
Copy link
Contributor

findleyr commented Nov 6, 2023

@dominikh for LSP edits it looks like inserts must precede replace edits, when all start at the same position: https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textEditArray.

We're naively translating go/analysis edits to LSP edits, and so that could explain the breakage.

Probably we should keep the edit semantics for go/analysis aligned with LSP, and so should clarify the go/analysis documentation for this case.

@dominikh
Copy link
Member

dominikh commented Nov 6, 2023

@findleyr can you test if dominikh/go-tools@4136a84 fixes the issue? This should respect the order required by the spec.

@tttoad
Copy link
Author

tttoad commented Nov 7, 2023

@hyangah dominikh/go-tools#1466. It works on my mac.

@tttoad
Copy link
Author

tttoad commented Nov 8, 2023

@findleyr @hyangah Any progress on the issue?

@findleyr
Copy link
Contributor

findleyr commented Nov 8, 2023

@tttoad thanks for testing -- I haven't yet had an opportunity to test for myself.

@tttoad what progress are you expecting? I think the correct path forward is to update staticcheck. This will take some time.

@dominikh
Copy link
Member

dominikh commented Nov 8, 2023

There are currently two competing fixes for the issue in Staticcheck. @tttoad's PR, that combines some of the edits, and my change that attempts to fix the issue by reordering edits.

I'm waiting to hear if my change fixes the issue before merging it to master (or before merging tttoad's PR.)

@findleyr
Copy link
Contributor

findleyr commented Nov 8, 2023

@dominikh thanks for clarifying.

It will likely take a few days before I can confirm. But I will do so.

@adonovan should we update the go/analysis documentation?

@adonovan
Copy link
Member

adonovan commented Nov 8, 2023

LSP ... inserts must precede replace edits, when all start at the same position
We're naively translating go/analysis edits to LSP edits, and so that could explain the breakage.
Probably we should keep the edit semantics for go/analysis aligned with LSP, and so should clarify the go/analysis documentation for this case.

That new requirement would represent a breaking change to the analysis API. The analysis API's specification is clear enough; let's just take more care in the logic that translates edits to LSP.

(Why does the LSP have that restriction? There's an unambiguous interpretation of any list of non-overlapping edits.)

@adonovan
Copy link
Member

adonovan commented Nov 9, 2023

There are at least a dozen places in gopls that loop over TextEdits, typically converting one form to another (e.g. protocol, analysis, gob). In many cases it's locally obvious that the edits are already ordered (e.g. because they came directly from diff.Strings), but it seems like a thorough audit is necessary. I think source.ApplyFix is the most obvious place where the invariant needs to be considered (and perhaps also lsp.refactorRewrite).

It would be nice if we could simplify those (at least a dozen) loops at the same time, and perhaps eliminate some of the conversions.

@findleyr
Copy link
Contributor

findleyr commented Dec 5, 2023

Sorry it took so long, but I tested dominikh/go-tools@4136a84 and confirmed that it fixes the issue. (the resulting code is not gofmt'ed, but that's OK).

@dominikh
Copy link
Member

dominikh commented Dec 5, 2023

Alright, thanks for confirming. I've pushed a version of that commit to Staticcheck's master, or you can cherry-pick the old version of that commit on top of the latest release (or you can wait until the next Staticcheck release, of course.)

@findleyr
Copy link
Contributor

findleyr commented Dec 10, 2023

Thanks. I think we'll wait for the next staticcheck release, as this has existed for a while.
(EDIT: milestoning for v0.16.0 (N+2), as a reminder to update staticcheck and add a test).

@findleyr findleyr added this to the gopls/v0.16.0 milestone Dec 10, 2023
@adonovan
Copy link
Member

adonovan commented May 17, 2024

I just confirmed empirically that both Emacs+eglot and VS Code are able to correctly apply a SuggestedFix whose TextEdits are out of order, so long as they are non-overlapping and non-adjacent.

I also confirmed that VS Code does the right thing for an insert of "A" at point p followed by a replacement of (p-1, p) by "B": these edits adjoin but do not overlap; the result is "BA". But Emacs+eglot misapplies the edits and yields "AB".

Both of these cases are consistent with the LSP spec wording, so I think Eglot has bug, but VS Code does not, and nor does staticcheck.

@findleyr findleyr removed this from the gopls/v0.16.0 milestone May 23, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
gopls Issues related to the Go language server, gopls. NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one. Tools This label describes issues relating to any tools in the x/tools repository.
Projects
None yet
Development

No branches or pull requests

6 participants