golang/go

x/tools/gopls: feature: code action to generate a stub function from a "no function f" type error

Closed this issue · 11 comments

xzbdmw commented

gopls version

golang.org/x/tools/gopls (devel)
golang.org/x/tools/gopls@(devel)
github.com/BurntSushi/toml@v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak=
github.com/google/go-cmp@v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
golang.org/x/exp/typeparams@v0.0.0-20221212164502-fae10dda9338 h1:2O2DON6y3XMJiQRAS1UWU+54aec2uopH3x7MAiqGW6Y=
golang.org/x/mod@v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0=
golang.org/x/sync@v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
golang.org/x/telemetry@v0.0.0-20240712210958-268b4a8ec2d7 h1:nU8/tAV/21mkPrCjACUeSibjhynTovgRMXc32+Y1Aec=
golang.org/x/text@v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
golang.org/x/tools@v0.21.1-0.20240508182429-e35e4ccd0d2d => ../
golang.org/x/vuln@v1.0.4 h1:SP0mPeg2PmGCu03V+61EcQiOjmpri2XijexKdzv8Z1I=
honnef.co/go/tools@v0.4.7 h1:9MDAWxMoSnB6QoSqiVr7P5mtkT9pOc1kSxchzPCnqJs=
mvdan.cc/gofumpt@v0.6.0 h1:G3QvahNDmpD+Aek/bNOLrFR2XC6ZAdo62dZu65gmwGo=
mvdan.cc/xurls/v2@v2.5.0 h1:lyBNOm8Wo71UknhUs4QTFUNNMyxy2JEIaKKo0RWOh+8=
go: go1.22.4

go env

GO111MODULE='auto'
GOARCH='arm64'
GOBIN=''
GOCACHE='/Users/xzb/Library/Caches/go-build'
GOENV='/Users/xzb/Library/Application Support/go/env'
GOEXE=''
GOEXPERIMENT=''
GOFLAGS=''
GOHOSTARCH='arm64'
GOHOSTOS='darwin'
GOINSECURE=''
GOMODCACHE='/Users/xzb/go/pkg/mod'
GONOPROXY=''
GONOSUMDB=''
GOOS='darwin'
GOPATH='/Users/xzb/go'
GOPRIVATE=''
GOPROXY='https://proxy.golang.org,direct'
GOROOT='/usr/local/go'
GOSUMDB='sum.golang.org'
GOTMPDIR=''
GOTOOLCHAIN='auto'
GOTOOLDIR='/usr/local/go/pkg/tool/darwin_arm64'
GOVCS=''
GOVERSION='go1.22.4'
GCCGO='gccgo'
AR='ar'
CC='clang'
CXX='clang++'
CGO_ENABLED='1'
GOMOD='/Users/xzb/Project/go/6.5840/src/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 -ffile-prefix-map=/var/folders/gv/r110hgbx1gbgzp95kf_q71x40000gn/T/go-build504977365=/tmp/go-build -gno-record-gcc-switches -fno-common

What did you do?

The | stands for cursor position

type foo struct{}

func (f *foo) fn() {
	f.bar|("string")
}

I expect a code action that auto generate the missing methods, result in

type foo struct{}

func (f *foo) fn() {
	f.bar("string")
}

func (f *foo) bar(s string) {
	// todo
}

What did you see happen?

There are no similar action

What did you expect to see?

provide the action
Here is similar action in rust-analyzer:
image

Editor and settings

No response

Logs

No response

gopls already has a related feature, stubmethods, that is admittedly a little hard to find. (The same is unfortunately also true of its documentation.)

This seems like a reasonable feature request for a new type-error analyzer.

xzbdmw commented

Nice, I'm looking at this feature, are there similar PRs that I can borrow from?

Nice, I'm looking at this feature, are there similar PRs that I can borrow from?

Search the codebase for stubmethods. It has two parts:

  • An analyzer, which analyzes a package and reports errors. Unlike most analyzers, which detect and report errors themselves, this one merely finds existing type errors of the right kind and reports a Diagnostic for each one. The diagnostic is allowed to suggest a fix. In this case the fix has SuggestedEdits.TextEdits=nil, meaning it is lazily computed by an ApplyFix("stubmethods") command.
  • An ApplyFix command handler for "stubmethods", that does the actual refactoring.

You should follow this structure. The stubmethods code may have nuggets you can steal/share.

Hmm, I notice the TODO comment not to do things this way. It might be simpler to avoid the analyzer altogether and just follow the code action plugin approach used by refactorRewriteRemoveUnusedParam, which also inspects pkg.TypeErrors().

@xzbdmw you may want to take a look at https://go.dev/cl/617035, which reorganizes stubmethods to fit directly into the CodeAction handler without using the go/analysis framework. Once this CL lands, I expect your change will follow the pattern used by stubmethods in the quickFix function in golang/codeaction.go, but instead of strings.Contains(msg, "missing method") it will look for "no method named...found" and produce a different code action.

xzbdmw commented

Is it possible to get the error node's type by type inference, so the generated function can have correct return value?

use info.Types[n] where n is the missing methods ast.CallExpr yield nil typeinfo.

If the info can not be retrieved easily, we need to guess the type by looking up parent node, eg. variable assignment, parameter assignment, return statement etc.

Is it possible to get the error node's type by type inference, so the generated function can have correct return value?

use info.Types[n] where n is the missing methods ast.CallExpr yield nil typeinfo.

If the info can not be retrieved easily, we need to guess the type by looking up parent node, eg. variable assignment, parameter assignment, return statement etc.

The type checker records the type of each expression, but not the type of each "hole" that is filled by that expression. This would be very useful information for a wide range of analyses, but unfortunately it is not available. Gopl's completion engine goes to great lengths to infer it, but not in a way that is easily reused.

Change https://go.dev/cl/617619 mentions this issue: gopls/internal: CodeAction: quickfix to generate missing method

Change https://go.dev/cl/621841 mentions this issue: gopls/internal: stubcalledfunction: improve logic of selecting insert position

@adonovan do you mind seeing a similar quick fix, but to declare a struct field? I couldn’t find it in the actual implementation.
If the idea is good, should I create another issue?