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: improve the 'implementation' query on interfaces #68641

Open
koonix opened this issue Jul 30, 2024 · 10 comments
Open

x/tools/gopls: improve the 'implementation' query on interfaces #68641

koonix opened this issue Jul 30, 2024 · 10 comments
Assignees
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

@koonix
Copy link

koonix commented Jul 30, 2024

gopls version

Build info
----------
golang.org/x/tools/gopls v0.16.1
    golang.org/x/tools/gopls@v0.16.1 h1:1hO/dCeUvjEYx3V0rVvCtOkwnpEpqS29paE+Jw4dcAc=
    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.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0=
    golang.org/x/sync@v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
    golang.org/x/telemetry@v0.0.0-20240607193123-221703e18637 h1:3Wt8mZlbFwG8llny+t18kh7AXxyWePFycXMuVdHxnyM=
    golang.org/x/text@v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
    golang.org/x/tools@v0.22.1-0.20240628205440-9c895dd76b34 h1:Kd+Z5Pm6uwYx3T2KEkeHMHUMZxDPb/q6b1m+zEcy62c=
    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.5

go env

GO111MODULE=''
GOARCH='amd64'
GOBIN='/home/koonix/.local/bin-go'
GOCACHE='/home/koonix/.cache/go-build'
GOENV='/home/koonix/.config/go/env'
GOEXE=''
GOEXPERIMENT=''
GOFLAGS=''
GOHOSTARCH='amd64'
GOHOSTOS='linux'
GOINSECURE=''
GOMODCACHE='/home/koonix/.local/share/go/pkg/mod'
GONOPROXY=''
GONOSUMDB=''
GOOS='linux'
GOPATH='/home/koonix/.local/share/go'
GOPRIVATE=''
GOPROXY='direct'
GOROOT='/usr/lib/golang'
GOSUMDB='off'
GOTMPDIR=''
GOTOOLCHAIN='local'
GOTOOLDIR='/usr/lib/golang/pkg/tool/linux_amd64'
GOVCS=''
GOVERSION='go1.22.5'
GCCGO='gccgo'
GOAMD64='v1'
AR='ar'
CC='gcc'
CXX='g++'
CGO_ENABLED='1'
GOMOD='/home/koonix/Repositories/other/gotest/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 -m64 -pthread -Wl,--no-gc-sections -fmessage-length=0 -ffile-prefix-map=/tmp/go-build3909477011=/tmp/go-build -gno-record-gcc-switches'

What did you do?

package main

type A interface {
	Read([]byte) (int, error)
}

type B interface {
	Read([]byte) (int, error)
	Extra()
}

type X int

func (x X) Read([]byte) (int, error) {
	return 0, nil
}

func fn(param A) {}

func main() {

	// this is fine
	var a A
	fn(a)

	// this is also fine
	var b B
	fn(b)

	// so is this
	var x X
	fn(x)
}

What did you see happen?

implementation behaves like so:

  • For interface A, it returns all types that implement it (including X but not B).
  • For type X, it returns all interfaces that it implements (including A).
  • For interface B, it returns no results.

What did you expect to see?

Much like the relationship between A and X, A is implemented by B, and B implements A. Therefore:

  • I expect to see B among A's implementations.
  • I expect to see A among B's implementations.

Editor and settings

No response

Logs

No response

@koonix koonix 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 Jul 30, 2024
@gopherbot gopherbot added this to the Unreleased milestone Jul 30, 2024
@adonovan
Copy link
Member

adonovan commented Aug 1, 2024

Thanks for the bug report; I can reproduce it with your test case. (The functions fn and main are superfluous, BTW.)

According to the documentation, implementation(A) should indeed include B, since B implements A, so this is definitely a bug. However, implementation(B) is correct not to include A, since A does not implement B.

type A interface { // impls={X} (should include B)
	Read([]byte) (int, error)
}

type B interface { // impls={} (correct)
	Read([]byte) (int, error)
	Extra()
}

type X int // impls={A} (correct)

func (x X) Read([]byte) (int, error) { return 0, nil }

@adonovan adonovan added the NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one. label Aug 1, 2024
@adonovan adonovan self-assigned this Aug 1, 2024
@koonix
Copy link
Author

koonix commented Aug 1, 2024

Thanks for your reply.

implementation(B) is correct not to include A, since A does not implement B

I understand that this is true according to the documentation, but could we change this?

What I was trying to show in main() was that the relationship between B and A is practically similar to that of X and A, and if implementation(X) contains A, perhaps implementation(B) should too.

In other words, concrete types can only implement, but interfaces can both implement and be implemented.

I understand that the textDocument/implementation request might not be a good place for this (backwards compatibility? not matching Microsoft's LSP spec?), but it would be nice to somehow be able to see what other interfaces an interface implements.

@tttoad
Copy link

tttoad commented Aug 4, 2024

@adonovan Is this a problem I can try to fix? I've been idle lately. 0.0

@hyangah hyangah modified the milestones: Unreleased, gopls/v0.17.0 Aug 4, 2024
@adonovan
Copy link
Member

adonovan commented Aug 5, 2024

[@koonix] I understand that this is true according to the documentation, but could we change this?
[...] concrete types can only implement, but interfaces can both implement and be implemented.

Certainly we could, and potentially even within the existing textDocument/implementation operation, if there's a consensus that this is the right change. The LSP spec gives very little guidance on what the implementation of a symbol actually means and what the operation is supposed to do. For example, the wording is loose enough to permit reporting the definition (as opposed to declaration) of a function symbol in C++, which distinguishes them. But I'm not yet convinced it is the right change: when I ask for all implementations of an interface, I would be surprised to see all interfaces that it implements as well. Unfortunately the LSP does not provide a pair of "upwards" and "downwards" operations for the respective halves of the query, and there is currently no way to generalize references-like queries. Perhaps this warrants a separate discussion.
[See https://github.com/microsoft/language-server-protocol/issues/2037]

[@tttoad] Is this a problem I can try to fix?

If you're referring to the bug portion of the issue (that implementations(A) should include B), then yes, we would welcome a fix. Thanks.

@gopherbot
Copy link
Contributor

Change https://go.dev/cl/603075 mentions this issue: gopls/implementation: Fix the query function when the implementation on the interface is a different interface.

@tttoad
Copy link

tttoad commented Aug 7, 2024

image Too much noise? Maybe we should sort, `local.struct`>`other.struct`>`local.interface`>`local.interface` ? @adonovan

@adonovan
Copy link
Member

adonovan commented Aug 9, 2024

Based on the description of the Go to Implementation feature in the VS Code manual, and despite my comment in the source, I'm no longer convinced that the gopls behavior is wrong. The manual says that when triggered by an interface type, it reports concrete types (subtypes), and when triggered by a concrete type, it reports interfaces (supertypes). That's what gopls does.

Of course, in Go, interfaces may have supertypes (also interfaces). And in languages like Java, concrete types may have subtypes (also concrete types). It would be useful in both these languages for LSP to separate the queries for supertypes and subtypes. But they do need to be separate queries: it makes no sense to report a mixture within a single operation.

One way for LSP to do this would be to support generalized reference queries.

[Update: a better one would be:]

@gopherbot
Copy link
Contributor

Change https://go.dev/cl/605395 mentions this issue: gopls/references: Fix the query function when the implementation on the interface is a different interface.

@gopherbot
Copy link
Contributor

Change https://go.dev/cl/619719 mentions this issue: gopls/internal/golang: implementation: report iface/iface relations

@findleyr findleyr modified the milestones: gopls/v0.17.0, gopls/v0.18.0 Nov 5, 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

7 participants