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: generate method stubs for a given interface #37537

Closed
marwan-at-work opened this issue Feb 28, 2020 · 14 comments
Closed

x/tools/gopls: generate method stubs for a given interface #37537

marwan-at-work opened this issue Feb 28, 2020 · 14 comments
Labels
FeatureRequest FrozenDueToAge gopls Issues related to the Go language server, gopls. release-blocker Tools This label describes issues relating to any tools in the x/tools repository.
Milestone

Comments

@marwan-at-work
Copy link
Contributor

marwan-at-work commented Feb 28, 2020

Proposal (TL;DR):

It would be great if Gopls implemented a way where a user:

  1. Moves the cursor to a type declaration
  2. Gets a list of all interface declarations within the workspace
  3. Picks an interface for gopls to generate method stubs for.

See this video for a demonstration: https://vimeo.com/394345925

Details

A common feature that Go and other typed languages have is to generate method stubs for an interface that a type wants to implement.

For most languages, the concrete type must declare what interface it intends to implement. This makes it easier for tools to generate those stubs.

For an example, take a look at this screenshot from TypeScript in VSCode:

Screen Shot 2020-02-27 at 1 59 07 PM

Notice that type X implements Greeter and therefore the dropdown knows which method it needs to stub out.

In Go, it is impossible to know that because interfaces are implicitly implemented.

Therefore, generating method stubs require a two step process:

  1. You pick the interface declaration that you'd like an interface to implement
  2. You pick the concrete type you'd like to add method-stubs to.

Therefore, I wrote a tool that that can do both of these features: https://github.com/marwan-at-work/impl

Here's a video of how I integrated the tool with VSCode locally to demonstrate the same feature working for Go: https://vimeo.com/394345925

However, the tool uses go/packages directly and is obviously a standalone that doesn't leverage gopls with its cache and environment. Therefore, I suggest we make this a feature within gopls.

If this is something worth including, I'd be very happy to port the work I did into gopls (with a little help).

Unfortunately, the LSP protocol does not have an explicit message for "generating method stubs", and so we a few options were brought up in slack that I want to highlight here:

  1. Use CodeAction -> refactor.extract / quickfix
  2. Use noneStandardRequest
  3. Hacking around Autocomplete

cc: @stamblerre @hyangah -- I'm not sure which of the above is the best way, but I'm happy to take on whichever you think is best 👍

PS. Note that this tool used to exist in GOPATH mode using https://github.com/josharian/impl but as far as I know, it is not modules aware and lacked a number of features such as "listing available interfaces" and "automatically adding import paths"

@marwan-at-work marwan-at-work added the gopls Issues related to the Go language server, gopls. label Feb 28, 2020
@gopherbot gopherbot added this to the Unreleased milestone Feb 28, 2020
@gopherbot gopherbot added the Tools This label describes issues relating to any tools in the x/tools repository. label Feb 28, 2020
@ianthehat
Copy link

Another possibility would be to add it as a quick fix to the

*MyImplementation does not implement MyInterface (missing MyMethod method)

type checking error.
You could intentionally trigger it with the

var _ MyInterface = (*MyImplementation)(nil)

stanza if needed.
Not saying this is the best way, just adding it to the list for consideration.

@marwan-at-work
Copy link
Contributor Author

marwan-at-work commented Feb 28, 2020

@ianthehat Would it be possible to expand it to wherever a concrete type is being casted as an interface? For example:

function GetWriter() io.Writer {
  return &myWriter{} // quick fix: implement io.Writer on *myWriter
}

function takeReader(r io.Reader) { ... }

takeReader(&myReader{}) // quick fix: implement io.Reader on *myReader

Similar to rename, it should only do it if the concrete type was defined within the workspace/module boundaries.

johnboyes added a commit to agilepathway/label-checker that referenced this issue Jul 5, 2020
It does not have support for go modules:
golang/go#37537
johnboyes added a commit to agilepathway/label-checker that referenced this issue Jul 5, 2020
It does not have support for go modules:
golang/go#37537
johnboyes added a commit to agilepathway/label-checker that referenced this issue Jul 5, 2020
It does not have support for go modules:
golang/go#37537
johnboyes added a commit to agilepathway/label-checker that referenced this issue Jul 5, 2020
* Enable Codespaces - add default Go files

Codespaces[1] allows us to use a remote vscode environment embedded in
the browser in GitHub[1]

This commit adds the pre-built container configuration[2] for Go[3].
We can customise this configuration as we wish in future commits.

[1] https://github.com/features/codespaces/
[2] https://docs.github.com/en/github/developing-online-with-codespaces/configuring-codespaces-for-your-project#using-a-pre-built-container-configuration
[3] https://github.com/microsoft/vscode-dev-containers/tree/master/containers/go

* Suppress/fix devcontainer Dockerfile warnings

These were hadolint warnings, and there were three of them, all on
.devcontainer/Dockerfile#L17:

DL3003 Use WORKDIR to switch to a directory

DL3008 Pin versions in apt get install.
Instead of `apt-get install <package>`
use `apt-get install <package>=<version>`

DL3015 Avoid additional packages by specifying `--no-install-recommends`

Suppressed the warnings apart from the DL3015 one, which was easy to
fix.

Not fixing the other warnings (for now anyway) as this Dockerfile is
code that has been lifted and shifted from the
`.devcontainer/Dockerfile` in:
https://github.com/microsoft/vscode-dev-containers/tree/master/containers/go

* Update module settings to "on" as we use modules

See:
https://dev.to/maelvls/why-is-go111module-everywhere-and-everything-about-go-modules-24k

* Add shellcheck vscode extension to devcontainer

https://www.shellcheck.net/
https://marketplace.visualstudio.com/items?itemName=timonwong.shellcheck

* Use vscode go language server on devcontainer

https://github.com/golang/vscode-go/blob/master/docs/gopls.md

* Stop installing go dependencies on devcontainer

There is no need to install them as we are using go modules - see:
microsoft/vscode-go#2836

* Set vscode editor go tabsize to correct size of 8

See:
microsoft/vscode-go#2479 (comment)

* Use golangci-lint for vscode devcontainer linting

https://github.com/golangci/golangci-lint

* No longer install Guru on devcontainer

It does not support Go modules:
https://github.com/golang/vscode-go/blob/master/docs/tools.md#guru

* Remove unnecessary gorename from devcontainer

Not needed when using go modules and gopls language server:
https://github.com/golang/vscode-go/blob/master/docs/tools.md#gorename

* Remove unnecessary godoctor from devcontainer

It is not needed when using go modules and the gopls language server:
https://github.com/golang/vscode-go/blob/master/docs/tools.md#godoctor

* Remove unnecessary goimports from devcontainer

Imports are instead handled by the gopls language server:
golang/go#33587 (comment)

* Remove unnecessary golint from devcontainer

Not needed as we are using golangci-lint

* Remove unnecessary gotests from devcontainer

gotests autogenerates table tests boilerplate code.  We don't need this
for this project.

* Remove unnecessary goplay module from devcontainer

We don't need this functionality:
https://github.com/haya14busa/goplay/

* Remove unnecessary gometalinter from devcontainer

Not needed as we are using golangci-lint for linting.

* Remove unnecessary impl module from devcontainer

It does not have support for go modules:
golang/go#37537

* Remove unnecessary fillstruct from devcontainer

fillstruct fills a struct literal with default values[1]
This functionality is now available in the gopls language server[2]

[1] https://github.com/davidrjenni/reftools/tree/master/cmd/fillstruct
[2] golang/go#37576 (comment)

* Add comment to explain why gopkgs is still needed

It is still needed even with gopls:
microsoft/vscode-go#3050 (comment)

* Fix installation of golangci-lint on devcontainer

Prior to this commit, vscode was saying "Analysis tools missing" and
prompting to install it.
Fix was to install golangci-lint via go get, as per:
golangci/golangci-lint#1037 (comment)

* Remove unnecessary gogetdoc from devcontainer

Similar functionality is available in the gopls language server:
fatih/vim-go#2808 (comment)

* Remove unnecessary revive linter from devcontainer

We are using golangci-lint for linting, so no need for revive.

* Remove unnecessary go-tools from devcontainer

go-tools primarily contains staticcheck, which we don't need as we are
using golangci-lint for linting.

* Output go version after building devcontainer

Doing this just to provide assurance that the container has started
successfully, after building.

* Move settings which are not container-specific

The guideline for`devcontainer.json` settings is that they should only
contain settings which _must_ be be changed in a container (e.g.
absolute paths)[1].

Moved the other settings to `.vscode/settings.json` so that they are
more visible, and easier to use when working locally (i.e. not in a
container).

[1] microsoft/vscode-remote-release#874 (comment)

* Up tests timeout as tests are slower on container

The tests seem to run much slower (c. 100 seconds compared to c. 50
seconds) when running on a remote container, compared to locally.
Will investigate to see if this is the same when running on Codespaces.

If the tests are taking 100 seconds we may need to create an issue to
speed them up.

* Add recommended vscode go settings

As per the recommendations at:
https://github.com/golang/tools/blob/master/gopls/doc/vscode.md

* Set dark colour theme for vscode in devcontainer

* Set devcontainer vscode to autosave after 500ms

Adding these settings to the devcontainer settings file rather than the
vscode settings file, as we want them to apply at the machine level.

https://code.visualstudio.com/docs/editor/codebasics#_save-auto-save
@stamblerre stamblerre removed this from the Unreleased milestone Jul 23, 2020
@stamblerre stamblerre added this to the gopls/unplanned milestone Oct 21, 2020
@gopherbot
Copy link
Contributor

Change https://golang.org/cl/274372 mentions this issue: gopls,internal/lsp: Implement method stubbing via CodeAction

@gopherbot
Copy link
Contributor

Change https://golang.org/cl/279412 mentions this issue: lsp/source: Update SuggestedFixFunc's signature to include source.Snapshot.

marwan-at-work added a commit to marwan-at-work/tools that referenced this issue Dec 23, 2020
This CL adds a quickfix CodeAction that detects "missing method"
compiler errors and suggests adding method stubs to the concrete
type that would implement the interface. There are many ways that
a user might indicate a concrete type is meant to be used as an interface.
This PR detects two types of those errors: variable declaration and function returns.
For variable declarations, things like the following should be detected:
1. var _ SomeInterface = SomeType{}
2. var _ = SomeInterface(SomeType{})
3. var _ SomeInterface = (*SomeType)(nil)
For function returns, the following example is the primary detection:
func newIface() SomeInterface {
	return &SomeType{}
}
More detections can be added in the future of course.

Fixes golang/go#37537

Change-Id: Ibb7784622184c9885eff2ccc786767682876b4d3
gopherbot pushed a commit to golang/tools that referenced this issue Sep 15, 2021
As part of the work for implementing method-stub code generation,
this CL updates the function signature of source/command.go's SuggestedFixFunc
so that a command can operate on the entire source.Snapshot to analyze
and change multiple packages and their files.

Updates golang/go#37537

Change-Id: I8430b2189ce7d91d37ab991f87ba368245293e56
Reviewed-on: https://go-review.googlesource.com/c/tools/+/279412
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
Trust: Rebecca Stambler <rstambler@golang.org>
Trust: Robert Findley <rfindley@google.com>
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
gopls-CI: kokoro <noreply+kokoro@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
marwan-at-work added a commit to marwan-at-work/tools that referenced this issue Sep 30, 2021
This CL adds a quickfix CodeAction that detects "missing method"
compiler errors and suggests adding method stubs to the concrete
type that would implement the interface. There are many ways that
a user might indicate a concrete type is meant to be used as an interface.
This PR detects two types of those errors: variable declaration and function returns.
For variable declarations, things like the following should be detected:
1. var _ SomeInterface = SomeType{}
2. var _ = SomeInterface(SomeType{})
3. var _ SomeInterface = (*SomeType)(nil)
For function returns, the following example is the primary detection:
func newIface() SomeInterface {
	return &SomeType{}
}
More detections can be added in the future of course.

Fixes golang/go#37537

Change-Id: Ibb7784622184c9885eff2ccc786767682876b4d3
marwan-at-work added a commit to marwan-at-work/tools that referenced this issue Oct 8, 2021
This CL adds a quickfix CodeAction that detects "missing method"
compiler errors and suggests adding method stubs to the concrete
type that would implement the interface. There are many ways that
a user might indicate a concrete type is meant to be used as an interface.
This PR detects two types of those errors: variable declaration and function returns.
For variable declarations, things like the following should be detected:
1. var _ SomeInterface = SomeType{}
2. var _ = SomeInterface(SomeType{})
3. var _ SomeInterface = (*SomeType)(nil)
For function returns, the following example is the primary detection:
func newIface() SomeInterface {
	return &SomeType{}
}
More detections can be added in the future of course.

Fixes golang/go#37537

Change-Id: Ibb7784622184c9885eff2ccc786767682876b4d3
@robert-zaremba
Copy link

Any ETA for this feature? go-impl has many problems (eg nested interfaces, package import alias ...)

@marwan-at-work
Copy link
Contributor Author

@robert-zaremba there's an open CL in gopls and is pending review: cc @findleyr

@hyangah
Copy link
Contributor

hyangah commented Jan 25, 2022

@marwan-at-work Sorry for letting you wait so long. Now adding custom gopls commands became easy. What's the advantage of "CodeAction -> refactor.extract / quickfix" compared to the custom gopls command as you did to replace the use of gopkgs before.

Related: see golang/vscode-go#1547 (pending CL: https://go-review.googlesource.com/c/vscode-go/+/330850/) that improves the current UX implemented using https://github.com/josharian/impl. Wonder if we can simply replace the old tool invocation once this is implemented as a gopls's custom command.

@marwan-at-work
Copy link
Contributor Author

marwan-at-work commented Jan 25, 2022

@hyangah I think there's a couple of differences:

  1. The "Add Import" needs each editor to implement. While quickfix (I imagine) is available in all/most editors with LSP support.
  2. In the case of this issue, a custom command will either require the user to write out the entire import path + interface name, or have VSCode list all the possible interfaces within a workspace (I prefer the latter one if it's acceptable) while the quickfix relies on a common convention of having the user write a concrete<->interface type assertion like var _ io.Writer = &myWriter{} AKA a poor man's "implements" keyword.

Wonder if we can simply replace the old tool invocation once this is implemented as a gopls's custom command.

We can certainly do that. However, the quickfix vs custom command part of the code is actually not where the complexity lies. It's the whole implementation of generating code within the environment and adding the proper import paths, which are things that josharian/impl never did AFAIK.

With all of that said, we can definitely have both a quickfix and a custom command that just re-uses the same logic 🎉

PS. I see in the CL you linked, you actually use a quick pick by searching the workspace for interface definitions (but I imagine that's not enough because you might want to implement interfaces of dependencies or std lib?). But in any case, I think my CL should hopefully provide the basis for both quickPick and quickFix.

@findleyr findleyr removed this from the gopls/unplanned milestone Feb 8, 2022
@findleyr findleyr added this to the gopls/v0.8.0 milestone Feb 8, 2022
@hyangah
Copy link
Contributor

hyangah commented Feb 14, 2022

PS. I see in the CL you linked, you actually use a quick pick by searching the workspace for interface definitions (but I imagine that's not enough because you might want to implement interfaces of dependencies or std lib?).

I don't know why :-) but currently workspace symbol search includes symbols from dependencies.
Screen Shot 2022-02-14 at 9 57 38 AM

But in any case, I think my CL should hopefully provide the basis for both quickPick and quickFix.

Yes, quick fix is a nice addition, but that doesn't replace the current vscode workflow. I see that part of your cl can be reused for the custom command that takes a position info & a desired interface name (import path + name).

@hyangah hyangah assigned marwan-at-work and heschi and unassigned hyangah Feb 15, 2022
marwan-at-work added a commit to marwan-at-work/tools that referenced this issue Mar 3, 2022
This CL adds a quickfix CodeAction that detects "missing method"
compiler errors and suggests adding method stubs to the concrete
type that would implement the interface. There are many ways that
a user might indicate a concrete type is meant to be used as an interface.
This PR detects two types of those errors: variable declaration and function returns.
For variable declarations, things like the following should be detected:
1. var _ SomeInterface = SomeType{}
2. var _ = SomeInterface(SomeType{})
3. var _ SomeInterface = (*SomeType)(nil)
For function returns, the following example is the primary detection:
func newIface() SomeInterface {
	return &SomeType{}
}
More detections can be added in the future of course.

Fixes golang/go#37537

Change-Id: Ibb7784622184c9885eff2ccc786767682876b4d3
marwan-at-work added a commit to marwan-at-work/tools that referenced this issue Mar 3, 2022
This CL adds a quickfix CodeAction that detects "missing method"
compiler errors and suggests adding method stubs to the concrete
type that would implement the interface. There are many ways that
a user might indicate a concrete type is meant to be used as an interface.
This PR detects two types of those errors: variable declaration and function returns.
For variable declarations, things like the following should be detected:
1. var _ SomeInterface = SomeType{}
2. var _ = SomeInterface(SomeType{})
3. var _ SomeInterface = (*SomeType)(nil)
For function returns, the following example is the primary detection:
func newIface() SomeInterface {
	return &SomeType{}
}
More detections can be added in the future of course.

Fixes golang/go#37537

Change-Id: Ibb7784622184c9885eff2ccc786767682876b4d3
@gopherbot
Copy link
Contributor

Change https://go.dev/cl/389654 mentions this issue: internal/lsp/source: support stubbing concrete type params

gopherbot pushed a commit to golang/tools that referenced this issue Mar 3, 2022
This CL adds support for generating method stubs for named types
that have type parameters and want to implement an interface.
See internal/lsp/testdata/stub/stub_generic_receiver.go for an example.
Note, this CL does not yet support type params on interface declarations.

Updates golang/go#37537

Change-Id: I2a2a18d364b2b489e2fbd8a74dfed88ae32d83b5
Reviewed-on: https://go-review.googlesource.com/c/tools/+/389654
Trust: Robert Findley <rfindley@google.com>
Run-TryBot: Robert Findley <rfindley@google.com>
Reviewed-by: Robert Findley <rfindley@google.com>
gopls-CI: kokoro <noreply+kokoro@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Trust: Suzy Mueller <suzmue@golang.org>
@gopherbot
Copy link
Contributor

Change https://go.dev/cl/404655 mentions this issue: internal/lsp/analysis/stubmethods: recognize *ast.CallExpr

gopherbot pushed a commit to golang/tools that referenced this issue May 17, 2022
This change makes it so that the stub methods analysis can recognize
errors happening to method and function call expressions that are being
passed a concrete type to an interface parameter. This way, a method stub CodeAction will appear at the call site.

Updates golang/go#37537

Change-Id: I886d53f06a85b9e5160d882aa742bb2b7fcea139
Reviewed-on: https://go-review.googlesource.com/c/tools/+/404655
gopls-CI: kokoro <noreply+kokoro@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Robert Findley <rfindley@google.com>
Reviewed-by: Suzy Mueller <suzmue@golang.org>
Run-TryBot: Suzy Mueller <suzmue@golang.org>
@marwan-at-work
Copy link
Contributor Author

I don't know why :-) but currently workspace symbol search includes symbols from dependencies.

@hyangah I am getting back to this a little bit and I see that this is actually the workspace/symbol request that does the search. However, the standard library is only included if your workspace happens to import those standard library packages. Therefore, if you import io but not bufio, then you only see results from the io package and not the bufio one.

I think it's potentially solvable by just making sure we search all standard library packages on the gopls side.

One thing that is quite confusing however is that the SymbolKind in gopls (or the LSP) starts counting from 1 but the SymbolKind in VSCode starts counting from 0. Is that something that's known or am I potentially looking at the wrong place?

VSCode: https://github.com/DefinitelyTyped/DefinitelyTyped/blob/d63ce78a85c1b9086eaf8c96499a30936b625495/types/vscode/index.d.ts#L3048

Gopls/LSP: https://github.com/golang/tools/blob/904e24e9fcf9e04175d563e5112b8264262acbb0/internal/lsp/protocol/tsprotocol.go#L6334

@findleyr
Copy link
Contributor

@marwan-at-work we can definitely make that improvement to workspace symbols.

I'm not sure about that typescript link, but in the protocol Interface is definitely 11:
https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#symbolKind

Also, we do get the correct interpretation in LSP clients.

@hyangah
Copy link
Contributor

hyangah commented May 27, 2022

@marwan-at-work

One thing that is quite confusing however is that the SymbolKind in gopls (or the LSP) starts counting from 1 but the SymbolKind in VSCode starts counting from 0. Is that something that's known or am I potentially looking at the wrong place?

Yes, LSP symbol kind is different from vscode symbol kind. Language clients are supposed to translate them (https://github.com/microsoft/vscode-languageserver-node/blob/f97bb73dbfb920af4bc8c13ecdcdc16359cdeda6/client/src/common/protocolConverter.ts#L838)

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
FeatureRequest FrozenDueToAge gopls Issues related to the Go language server, gopls. release-blocker 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