From b7532e6ca5d783143bf3750e63de0d5cb9e7d4ce Mon Sep 17 00:00:00 2001 From: Rob Findley Date: Tue, 3 Dec 2024 21:57:39 +0000 Subject: [PATCH 01/73] gopls/doc: update relnotes for v0.17.0 Update release notes and feature documentation for change signature refactoring and support changes. For golang/go#70301 Change-Id: I1a4543e317f1016cdd5631640307b60435f1b23e Reviewed-on: https://go-review.googlesource.com/c/tools/+/633376 LUCI-TryBot-Result: Go LUCI Reviewed-by: Alan Donovan --- gopls/doc/features/transformation.md | 94 +++++++++++++++---- gopls/doc/release/v0.16.0.md | 16 ++-- gopls/doc/release/v0.17.0.md | 133 +++++++++++++++++++++------ 3 files changed, 187 insertions(+), 56 deletions(-) diff --git a/gopls/doc/features/transformation.md b/gopls/doc/features/transformation.md index bfc30eb21d0..7d32c24ca93 100644 --- a/gopls/doc/features/transformation.md +++ b/gopls/doc/features/transformation.md @@ -6,6 +6,7 @@ formatting, simplifications), code repair (fixes), and editing support (filling in struct literals and switch statements). Code transformations are not a single category in the LSP: + - A few, such as Formatting and Rename, are primary operations in the protocol. - Some transformations are exposed through [Code Lenses](../codelenses.md), @@ -43,6 +44,7 @@ or to cause the server to send other requests to the client, such as a `showDocument` request to open a report in a web browser. The main difference between code lenses and code actions is this: + - a `codeLens` request obtains commands for the entire file. Each command specifies its applicable source range, and typically appears as an annotation on that source range. @@ -82,6 +84,8 @@ Gopls supports the following code actions: - [`refactor.rewrite.joinLines`](#refactor.rewrite.joinLines) - [`refactor.rewrite.removeUnusedParam`](#refactor.rewrite.removeUnusedParam) - [`refactor.rewrite.splitLines`](#refactor.rewrite.splitLines) +- [`refactor.rewrite.moveParamLeft`](#refactor.rewrite.moveParamLeft) +- [`refactor.rewrite.moveParamRight`](#refactor.rewrite.moveParamRight) Gopls reports some code actions twice, with two different kinds, so that they appear in multiple UI elements: simplifications, @@ -99,6 +103,7 @@ that, in the course of reporting a diagnostic about a problem, also suggest a fix. A `codeActions` request will return any fixes accompanying diagnostics for the current selection. + Caveats: + - Many of gopls code transformations are limited by Go's syntax tree representation, which currently records comments not in the tree but in a side table; consequently, transformations such as Extract @@ -158,10 +164,12 @@ Most clients are configured to format files and organize imports whenever a file is saved. Settings: + - The [`gofumpt`](../settings.md#gofumpt) setting causes gopls to use an alternative formatter, [`github.com/mvdan/gofumpt`](https://pkg.go.dev/mvdan.cc/gofumpt). Client support: + - **VS Code**: Formats on save by default. Use `Format document` menu item (`⌥⇧F`) to invoke manually. - **Emacs + eglot**: Use `M-x eglot-format-buffer` to format. Attach it to `before-save-hook` to format on save. For formatting combined with organize-imports, many users take the legacy approach of setting `"goimports"` as their `gofmt-command` using [go-mode](https://github.com/dominikh/go-mode.el), and adding `gofmt-before-save` to `before-save-hook`. An LSP-based solution requires code such as https://github.com/joaotavora/eglot/discussions/1409. - **CLI**: `gopls format file.go` @@ -194,6 +202,7 @@ Settings: should appear after standard and third-party packages in the sort order. Client support: + - **VS Code**: automatically invokes `source.organizeImports` before save. To disable it, use the snippet below, and invoke the "Organize Imports" command manually as needed. ``` @@ -243,7 +252,7 @@ boolean. **Method receivers**: When testing a method `T.F` or `(*T).F`, the test must construct an instance of T to pass as the receiver. Gopls searches the package -for a suitable function that constructs a value of type T or *T, optionally with +for a suitable function that constructs a value of type T or \*T, optionally with an error, preferring a function named `NewT`. **Imports**: Gopls adds missing imports to the test file, using the last @@ -305,9 +314,10 @@ Similar problems may arise with packages that use reflection, such as judgment and testing. Some tips for best results: + - There is currently no special support for renaming all receivers of a family of methods at once, so you will need to rename one receiver - one at a time (golang/go#41892). + one at a time (golang/go#41892). - The safety checks performed by the Rename algorithm require type information. If the program is grossly malformed, there may be insufficient information for it to run (golang/go#41870), @@ -328,12 +338,12 @@ in the latter half of this 2015 GothamGo talk: [Using go/types for Code Comprehension and Refactoring Tools](https://www.youtube.com/watch?v=p_cz7AxVdfg). Client support: + - **VS Code**: Use "[Rename symbol](https://code.visualstudio.com/docs/editor/editingevolved#_rename-symbol)" menu item (`F2`). - **Emacs + eglot**: Use `M-x eglot-rename`, or `M-x go-rename` from [go-mode](https://github.com/dominikh/go-mode.el). - **Vim + coc.nvim**: Use the `coc-rename` command. - **CLI**: `gopls rename file.go:#offset newname` - ## `refactor.extract`: Extract function/method/variable @@ -392,7 +402,6 @@ The following Extract features are planned for 2024 but not yet supported: interface type with all the methods of the selected concrete type; see golang/go#65721 and golang/go#46665. - ## `refactor.extract.toNewFile`: Extract declarations to new file @@ -409,8 +418,8 @@ first token of the declaration, such as `func` or `type`. ![Before: select the declarations to move](../assets/extract-to-new-file-before.png) ![After: the new file is based on the first symbol name](../assets/extract-to-new-file-after.png) - + ## `refactor.inline.call`: Inline call to function For a `codeActions` request where the selection is (or is within) a @@ -418,6 +427,7 @@ call of a function or method, gopls will return a command of kind `refactor.inline.call`, whose effect is to inline the function call. The screenshots below show a call to `sum` before and after inlining: + + ![Before: select Refactor... Inline call to sum](../inline-before.png) ![After: the call has been replaced by the sum logic](../inline-after.png) @@ -484,13 +495,16 @@ func f(s string) { fmt.Println(s) } ``` + a call `f("hello")` will be inlined to: + ```go func() { defer fmt.Println("goodbye") fmt.Println("hello") }() ``` + Although the parameter was eliminated, the function call remains. An inliner is a bit like an optimizing compiler. @@ -539,18 +553,17 @@ Here are some of the technical challenges involved in sound inlining: `Printf` by qualified references such as `fmt.Printf`, and add an import of package `fmt` as needed. -- **Implicit conversions:** When passing an argument to a function, it - is implicitly converted to the parameter type. - If we eliminate the parameter variable, we don't want to - lose the conversion as it may be important. - For example, in `func f(x any) { y := x; fmt.Printf("%T", &y) }` the - type of variable y is `any`, so the program prints `"*interface{}"`. - But if inlining the call `f(1)` were to produce the statement `y := - 1`, then the type of y would have changed to `int`, which could - cause a compile error or, as in this case, a bug, as the program - now prints `"*int"`. When the inliner substitutes a parameter variable - by its argument value, it may need to introduce explicit conversions - of each value to the original parameter type, such as `y := any(1)`. +- **Implicit conversions:** When passing an argument to a function, it is + implicitly converted to the parameter type. If we eliminate the parameter + variable, we don't want to lose the conversion as it may be important. For + example, in `func f(x any) { y := x; fmt.Printf("%T", &y) }` the type of + variable y is `any`, so the program prints `"*interface{}"`. But if inlining + the call `f(1)` were to produce the statement `y := 1`, then the type of y + would have changed to `int`, which could cause a compile error or, as in this + case, a bug, as the program now prints `"*int"`. When the inliner substitutes + a parameter variable by its argument value, it may need to introduce explicit + conversions of each value to the original parameter type, such as `y := + any(1)`. - **Last reference:** When an argument expression has no effects and its corresponding parameter is never used, the expression @@ -577,6 +590,7 @@ code actions whose kinds are children of `refactor.rewrite`. The [`unusedparams` analyzer](../analyzers.md#unusedparams) reports a diagnostic for each parameter that is not used within the function body. For example: + ```go func f(x, y int) { // "unused parameter: x" fmt.Println(y) @@ -607,6 +621,50 @@ Observe that in the first call, the argument `chargeCreditCard()` was not deleted because of potential side effects, whereas in the second call, the argument 2, a constant, was safely deleted. + + +### `refactor.rewrite.moveParam{Left,Right}`: Move function parameters + +When the selection is a parameter in a function or method signature, gopls +offers a code action to move the parameter left or right (if feasible), +updating all callers accordingly. + +For example: + +```go +func Foo(x, y int) int { + return x + y +} + +func _() { + _ = Foo(0, 1) +} +``` + +becomes + +```go +func Foo(y, x int) int { + return x + y +} + +func _() { + _ = Foo(1, 0) +} +``` + +following a request to move `x` right, or `y` left. + +This is a primitive building block of more general "Change signature" +operations. We plan to generalize this to arbitrary signature rewriting, but +the language server protocol does not currently offer good support for user +input into refactoring operations (see +[microsoft/language-server-protocol#1164](https://github.com/microsoft/language-server-protocol/issues/1164)). +Therefore, any such refactoring will require custom client-side logic. (As a +very hacky workaround, you can express arbitrary parameter movement by invoking +Rename on the `func` keyword of a function declaration, but this interface is +just a temporary stopgap.) + ### `refactor.rewrite.changeQuote`: Convert string literal between raw and interpreted @@ -673,6 +731,7 @@ func() ( z rune, ) ``` + Observe that in the last two cases, each [group](https://pkg.go.dev/go/ast#Field) of parameters or results is treated as a single item. @@ -683,6 +742,7 @@ respectively, or trivial (fewer than two items). These code actions are not offered for lists containing `//`-style comments, which run to the end of the line. + diff --git a/gopls/doc/release/v0.16.0.md b/gopls/doc/release/v0.16.0.md index 1bcb5ec3d06..7ee2775e9b1 100644 --- a/gopls/doc/release/v0.16.0.md +++ b/gopls/doc/release/v0.16.0.md @@ -1,12 +1,7 @@ # gopls/v0.16.0 - ``` -go install golang.org/x/tools/gopls@v0.16.0-pre.2 +go install golang.org/x/tools/gopls@v0.16.2 ``` This release includes several features and bug fixes, and is the first @@ -24,6 +19,7 @@ and also the last to support integrating with go command versions 1.19 and a message advising the user to upgrade. When using gopls, there are three versions to be aware of: + 1. The _gopls build go version_: the version of Go used to build gopls. 2. The _go command version_: the version of the go list command executed by gopls to load information about your workspace. @@ -97,6 +93,7 @@ cause your editor to navigate to the declaration. Editor support: + - VS Code: use the "Source action > Browse documentation for func fmt.Println" menu item. Note: source links navigate the editor but don't yet raise the window yet. Please upvote microsoft/vscode#208093 and microsoft/vscode#207634 (temporarily closed). @@ -106,7 +103,6 @@ The `linksInHover` setting now supports a new value, `"gopls"`, that causes documentation links in the the Markdown output of the Hover operation to link to gopls' internal doc viewer. - ### Browse free symbols Gopls offers another web-based code action, "Browse free symbols", @@ -133,6 +129,7 @@ the function by choosing a different type for that parameter. Editor support: + - VS Code: use the `Source action > Browse free symbols` menu item. - Emacs: requires eglot v1.17. Use `M-x go-browse-freesymbols` from github.com/dominikh/go-mode.el. @@ -157,12 +154,14 @@ Gopls cannot yet display assembly for generic functions: generic functions are not fully compiled until they are instantiated, but any function declaration enclosing the selection cannot be an instantiated generic function. + Editor support: + - VS Code: use the "Source action > Browse assembly for f" menu item. - Emacs: requires eglot v1.17. Use `M-x go-browse-assembly` from github.com/dominikh/go-mode.el. @@ -252,8 +251,7 @@ suboptimal ordering of struct fields, if this figure is 20% or higher: In the struct above, alignment rules require each of the two boolean -fields (1 byte) to occupy a complete word (8 bytes), leading to (7 + -7) / (3 * 8) = 58% waste. +fields (1 byte) to occupy a complete word (8 bytes), leading to (7 + 7) / (3 \* 8) = 58% waste. Placing the two booleans together would save a word. This information may be helpful when making space optimizations to diff --git a/gopls/doc/release/v0.17.0.md b/gopls/doc/release/v0.17.0.md index 1a278b013cb..3da16e831e2 100644 --- a/gopls/doc/release/v0.17.0.md +++ b/gopls/doc/release/v0.17.0.md @@ -1,8 +1,67 @@ +# gopls/v0.17.0 + + + +``` +go install golang.org/x/tools/gopls@v0.17.0-pre.4 +``` + +# New support policies + +With this release, we are narrowing our official support window to align with +the [Go support policy](https://go.dev/doc/devel/release#policy). This will +reduce the considerable costs to us of testing against older Go versions, +allowing us to spend more time fixing bugs and adding features that benefit the +majority of gopls users who run recent versions of Go. + +This narrowing is occuring in two dimensions: **build compatibility** refers to +the versions of the Go toolchain that can be used to build gopls, and **go +command compatibility** refers to the versions of the `go` command that can be +used by gopls to list information about packages and modules in your workspace. + +## Build compatibility: the most recent major Go version + +As described in the [v0.16.0 release +notes](https://github.com/golang/tools/releases/tag/gopls%2Fv0.16.0), building the +latest version of gopls will now require the latest major version of the Go +toolchain. Therefore this release (gopls@v0.17.0) must be built with Go 1.23.0 +or later. Thanks to [automatic toolchain +upgrades](https://go.dev/blog/toolchain), if your system Go version is at least +Go 1.21.0 and you have `GOTOOLCHAIN=auto` set (the default), the `go` command +will automatically upgrade your Go toolchain as needed. This upgrade should be +completely transparent, similar to upgrading a module dependency. + +## Go command compatibility: the 2 most recent major Go versions + +The gopls@v0.17.x releases will be the final versions of gopls to nominally +support integrating with more than the 2 most recent Go releases. In the past, +we implied "best effort" support for up to 4 versions, though in practice we +did not have resources to fix bugs that were present only with older Go +versions. With gopls@v0.17.0, we narrowed this best effort support to 3 +versions, primarily because users need at least Go 1.21 to benefit from +automatic toolchain upgrades (see above). + +Starting with gopls@v0.18.0, we will officially support integrating with only +the 2 most recent major versions of the `go` command. This is consistent with +the Go support policy. See golang/go#69321 (or [this +comment](https://github.com/golang/go/issues/69321#issuecomment-2344996677) +specifically) for details. + +We won't prevent gopls from being used with older Go versions (just as we +don't disallow integration with arbitrary +[`go/packages`](https://pkg.go.dev/golang.org/x/tools/go/packages) drivers), +but we won't run integration tests against older Go versions, and won't fix +bugs that are only present when used with old Go versions. + # Configuration Changes - The `fieldalignment` analyzer, previously disabled by default, has been removed: it is redundant with the hover size/offset information displayed by v0.16.0 and its diagnostics were confusing. +- The `undeclaredname` analyzer has been replaced with an ordinary code action. - The kind (identifiers) of all of gopls' code actions have changed to use more specific hierarchical names. For example, "Inline call" has changed from `refactor.inline` to `refactor.inline.call`. @@ -13,16 +72,31 @@ # New features -## Change signature refactoring +## Refactoring + +This release contains a number of new features related to refactoring. +Additionally, it fixes [many +bugs](https://github.com/golang/go/issues?q=is%3Aissue+milestone%3Agopls%2Fv0.17.0+label%3ARefactoring+is%3Aclosed) +in existing refactoring operations, primarily related to **extract**, and **inline**. -TODO(rfindley): document the state of change signature refactoring once the -feature set stabilizes. +These improvements move us toward a longer term goal of offering a more robust +and complete set of refactoring tools. We still have [much to +do](https://github.com/golang/go/issues?q=is%3Aissue+label%3Agopls+label%3ARefactoring+is%3Aopen+), +and this effort will continue into 2025. -## Improvements to existing refactoring operations +### Move parameter refactorings -TODO(rfindley): document the full set of improvements to rename/extract/inline. +Gopls now offers code actions to move function and method parameters left or +right in the function signature, updating all callers. -## Extract declarations to new file +Unfortunately, there is no native LSP operation that provides a good user +interface for arbitrary "change signature" refactoring. We plan to build such +an interface within VS Code. In the short term, we have made it possible to +express more complicated parameter transformations by invoking 'rename' on the +'func' keyword. This user interface is a temporary stop-gap until a better +mechanism is available for LSP commands that enable client-side dialogs. + +### Extract declarations to new file Gopls now offers another code action, "Extract declarations to new file" (`refactor.extract.toNewFile`), @@ -38,7 +112,7 @@ or by selecting a whole declaration or multiple declarations. In order to avoid ambiguity and surprise about what to extract, some kinds of paritial selection of a declaration cannot invoke this code action. -## Extract constant +### Extract constant When the selection is a constant expression, gopls now offers "Extract constant" instead of "Extract variable", and generates a `const` @@ -47,7 +121,27 @@ declaration instead of a local variable. Also, extraction of a constant or variable now works at top-level, outside of any function. -## Pull diagnostics +### Generate missing method from function call + +When you attempt to call a method on a type that lacks that method, the +compiler will report an error like “type T has no field or method f”. Gopls now +offers a new code action, “Declare missing method of T.f”, where T is the +concrete type and f is the undefined method. The stub method's signature is +inferred from the context of the call. + +### Generate a test for a function or method + +If the selected chunk of code is part of a function or method declaration F, +gopls will offer the "Add test for F" code action, which adds a new test for the +selected function in the corresponding `_test.go` file. The generated test takes +into account its signature, including input parameters and results. + +Since this feature is implemented by the server (gopls), it is compatible with +all LSP-compliant editors. VS Code users may continue to use the client-side +`Go: Generate Unit Tests For file/function/package` command, which runs the +[gotests](https://github.com/cweill/gotests) tool. + +## Initial support for pull diagnostics When initialized with the option `"pullDiagnostics": true`, gopls will advertise support for the `textDocument.diagnostic` @@ -55,7 +149,7 @@ When initialized with the option `"pullDiagnostics": true`, gopls will advertise which allows editors to request diagnostics directly from gopls using a `textDocument/diagnostic` request, rather than wait for a `textDocument/publishDiagnostics` notification. This feature is off by default -until the performance of pull diagnostics is comparable to push diagnostics. +until the feature set of pull diagnostics is comparable to push diagnostics. ## Hover improvements @@ -88,15 +182,6 @@ or assembly, the function has no body. Executing a second Definition query (while already at the Go declaration) will navigate you to the assembly implementation. -## Generate missing method from function call - -When you attempt to call a method on a type that does not have that method, -the compiler will report an error like “type X has no field or method Y”. -Gopls now offers a new code action, “Declare missing method of T.f”, -where T is the concrete type and f is the undefined method. -The stub method's signature is inferred -from the context of the call. - ## `yield` analyzer The new `yield` analyzer detects mistakes using the `yield` function @@ -111,15 +196,3 @@ causing `Add` to race with `Wait`. (This check is equivalent to [staticcheck's SA2000](https://staticcheck.dev/docs/checks#SA2000), but is enabled by default.) - -## Add test for function or method - -If the selected chunk of code is part of a function or method declaration F, -gopls will offer the "Add test for F" code action, which adds a new test for the -selected function in the corresponding `_test.go` file. The generated test takes -into account its signature, including input parameters and results. - -Since this feature is implemented by the server (gopls), it is compatible with -all LSP-compliant editors. VS Code users may continue to use the client-side -`Go: Generate Unit Tests For file/function/package` command which utilizes the -[gotests](https://github.com/cweill/gotests) tool. \ No newline at end of file From bf32f4d87472d553f6b3ee94d289e7a7158369e1 Mon Sep 17 00:00:00 2001 From: Rob Findley Date: Mon, 9 Dec 2024 18:20:54 +0000 Subject: [PATCH 02/73] gopls/doc/release: tweak wording following feedback Adjust wording about 'automatically upgrading' the Go toolchain to make it sound less like the system toolchain is affected. Thanks to Ethan Reesor for feedback in the Gopher's Slack. Change-Id: Ibb865db9c8f12d314d3c6b63fe9e8aabf7803e8c Reviewed-on: https://go-review.googlesource.com/c/tools/+/634635 Auto-Submit: Robert Findley LUCI-TryBot-Result: Go LUCI Reviewed-by: Alan Donovan --- gopls/doc/release/v0.17.0.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gopls/doc/release/v0.17.0.md b/gopls/doc/release/v0.17.0.md index 3da16e831e2..e6af9c6bf26 100644 --- a/gopls/doc/release/v0.17.0.md +++ b/gopls/doc/release/v0.17.0.md @@ -31,8 +31,8 @@ toolchain. Therefore this release (gopls@v0.17.0) must be built with Go 1.23.0 or later. Thanks to [automatic toolchain upgrades](https://go.dev/blog/toolchain), if your system Go version is at least Go 1.21.0 and you have `GOTOOLCHAIN=auto` set (the default), the `go` command -will automatically upgrade your Go toolchain as needed. This upgrade should be -completely transparent, similar to upgrading a module dependency. +will automatically download the new Go toolchain as needed, similar to +upgrading a module dependency. ## Go command compatibility: the 2 most recent major Go versions From 98c17f1e47ca9d2ee86c6d4de8ffa14bfadfe587 Mon Sep 17 00:00:00 2001 From: Alan Donovan Date: Wed, 4 Dec 2024 15:00:45 -0500 Subject: [PATCH 03/73] gopls/internal/golang: eliminate CollectScopes This CL moves it into golang/completion and melts it down. (Most uses can be replaced by fileScope.Innermost and Scope.LookupParent.) Change-Id: Ieeadcd1327bc5719de53bd188d3b52ae9124aa59 Reviewed-on: https://go-review.googlesource.com/c/tools/+/633720 Auto-Submit: Alan Donovan Reviewed-by: Robert Findley LUCI-TryBot-Result: Go LUCI Commit-Queue: Alan Donovan --- .../internal/golang/completion/completion.go | 33 ++++++++++++++----- gopls/internal/golang/util.go | 26 --------------- 2 files changed, 25 insertions(+), 34 deletions(-) diff --git a/gopls/internal/golang/completion/completion.go b/gopls/internal/golang/completion/completion.go index 7b4abe774a4..3921eebf260 100644 --- a/gopls/internal/golang/completion/completion.go +++ b/gopls/internal/golang/completion/completion.go @@ -294,6 +294,11 @@ type completer struct { // including nil values for nodes that don't defined a scope. It // also includes our package scope and the universal scope at the // end. + // + // (It is tempting to replace this with fileScope.Innermost(pos) + // and simply follow the Scope.Parent chain, but we need to + // preserve the pairwise association of scopes[i] and path[i] + // because there is no way to get from the Scope to the Node.) scopes []*types.Scope } @@ -530,6 +535,8 @@ func Completion(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, p return nil, nil, fmt.Errorf("cannot find node enclosing position") } + info := pkg.TypesInfo() + // Check if completion at this position is valid. If not, return early. switch n := path[0].(type) { case *ast.BasicLit: @@ -548,7 +555,7 @@ func Completion(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, p } case *ast.Ident: // Don't offer completions for (most) defining identifiers. - if obj, ok := pkg.TypesInfo().Defs[n]; ok { + if obj, ok := info.Defs[n]; ok { if v, ok := obj.(*types.Var); ok && v.IsField() && v.Embedded() { // Allow completion of anonymous fields, since they may reference type // names. @@ -570,21 +577,31 @@ func Completion(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, p } } - // Collect all surrounding scopes, innermost first. - scopes := golang.CollectScopes(pkg.TypesInfo(), path, pos) + // Collect all surrounding scopes, innermost first, inserting + // nils as needed to preserve the correspondence with path[i]. + var scopes []*types.Scope + for _, n := range path { + switch node := n.(type) { + case *ast.FuncDecl: + n = node.Type + case *ast.FuncLit: + n = node.Type + } + scopes = append(scopes, info.Scopes[n]) + } scopes = append(scopes, pkg.Types().Scope(), types.Universe) var goversion string // "" => no version check // Prior go1.22, the behavior of FileVersion is not useful to us. if slices.Contains(build.Default.ReleaseTags, "go1.22") { - goversion = versions.FileVersion(pkg.TypesInfo(), pgf.File) // may be "" + goversion = versions.FileVersion(info, pgf.File) // may be "" } opts := snapshot.Options() c := &completer{ pkg: pkg, snapshot: snapshot, - qf: typesutil.FileQualifier(pgf.File, pkg.Types(), pkg.TypesInfo()), + qf: typesutil.FileQualifier(pgf.File, pkg.Types(), info), mq: golang.MetadataQualifierForFile(snapshot, pgf.File, pkg.Metadata()), completionContext: completionContext{ triggerCharacter: protoContext.TriggerCharacter, @@ -598,8 +615,8 @@ func Completion(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, p path: path, pos: pos, seen: make(map[types.Object]bool), - enclosingFunc: enclosingFunction(path, pkg.TypesInfo()), - enclosingCompositeLiteral: enclosingCompositeLiteral(path, pos, pkg.TypesInfo()), + enclosingFunc: enclosingFunction(path, info), + enclosingCompositeLiteral: enclosingCompositeLiteral(path, pos, info), deepState: deepCompletionState{ enabled: opts.DeepCompletion, }, @@ -1612,7 +1629,7 @@ func (c *completer) lexical(ctx context.Context) error { for _, name := range scope.Names() { declScope, obj := scope.LookupParent(name, c.pos) if declScope != scope { - continue // Name was declared in some enclosing scope, or not at all. + continue // scope of name starts after c.pos } // If obj's type is invalid, find the AST node that defines the lexical block diff --git a/gopls/internal/golang/util.go b/gopls/internal/golang/util.go index be5c7c0a735..6267b6c2245 100644 --- a/gopls/internal/golang/util.go +++ b/gopls/internal/golang/util.go @@ -18,7 +18,6 @@ import ( "golang.org/x/tools/gopls/internal/cache/metadata" "golang.org/x/tools/gopls/internal/cache/parsego" "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/util/astutil" "golang.org/x/tools/gopls/internal/util/bug" "golang.org/x/tools/gopls/internal/util/safetoken" "golang.org/x/tools/internal/tokeninternal" @@ -130,31 +129,6 @@ func findFileInDeps(s metadata.Source, mp *metadata.Package, uri protocol.Docume return search(mp) } -// CollectScopes returns all scopes in an ast path, ordered as innermost scope -// first. -// -// TODO(adonovan): move this to golang/completion and simplify to use -// Scopes.Innermost and LookupParent instead. -func CollectScopes(info *types.Info, path []ast.Node, pos token.Pos) []*types.Scope { - // scopes[i], where i Date: Mon, 9 Dec 2024 09:41:47 -0500 Subject: [PATCH 04/73] internal/typeparams: delete GenericAssignableTo It is unused and, in the one place we actually wanted to use it, it was the wrong tool for the job; what we need is unification. Updates golang/go#59224 Updates golang/go#63982 Change-Id: I05b6fe6f56e3f81f434e76398c20496950822bfb Reviewed-on: https://go-review.googlesource.com/c/tools/+/634597 LUCI-TryBot-Result: Go LUCI Reviewed-by: Robert Findley --- internal/typeparams/common.go | 72 ------------------------------ internal/typeparams/common_test.go | 71 ----------------------------- 2 files changed, 143 deletions(-) diff --git a/internal/typeparams/common.go b/internal/typeparams/common.go index 0b84acc5c7f..cdae2b8e818 100644 --- a/internal/typeparams/common.go +++ b/internal/typeparams/common.go @@ -66,75 +66,3 @@ func IsTypeParam(t types.Type) bool { _, ok := types.Unalias(t).(*types.TypeParam) return ok } - -// GenericAssignableTo is a generalization of types.AssignableTo that -// implements the following rule for uninstantiated generic types: -// -// If V and T are generic named types, then V is considered assignable to T if, -// for every possible instantiation of V[A_1, ..., A_N], the instantiation -// T[A_1, ..., A_N] is valid and V[A_1, ..., A_N] implements T[A_1, ..., A_N]. -// -// If T has structural constraints, they must be satisfied by V. -// -// For example, consider the following type declarations: -// -// type Interface[T any] interface { -// Accept(T) -// } -// -// type Container[T any] struct { -// Element T -// } -// -// func (c Container[T]) Accept(t T) { c.Element = t } -// -// In this case, GenericAssignableTo reports that instantiations of Container -// are assignable to the corresponding instantiation of Interface. -func GenericAssignableTo(ctxt *types.Context, V, T types.Type) bool { - V = types.Unalias(V) - T = types.Unalias(T) - - // If V and T are not both named, or do not have matching non-empty type - // parameter lists, fall back on types.AssignableTo. - - VN, Vnamed := V.(*types.Named) - TN, Tnamed := T.(*types.Named) - if !Vnamed || !Tnamed { - return types.AssignableTo(V, T) - } - - vtparams := VN.TypeParams() - ttparams := TN.TypeParams() - if vtparams.Len() == 0 || vtparams.Len() != ttparams.Len() || VN.TypeArgs().Len() != 0 || TN.TypeArgs().Len() != 0 { - return types.AssignableTo(V, T) - } - - // V and T have the same (non-zero) number of type params. Instantiate both - // with the type parameters of V. This must always succeed for V, and will - // succeed for T if and only if the type set of each type parameter of V is a - // subset of the type set of the corresponding type parameter of T, meaning - // that every instantiation of V corresponds to a valid instantiation of T. - - // Minor optimization: ensure we share a context across the two - // instantiations below. - if ctxt == nil { - ctxt = types.NewContext() - } - - var targs []types.Type - for i := 0; i < vtparams.Len(); i++ { - targs = append(targs, vtparams.At(i)) - } - - vinst, err := types.Instantiate(ctxt, V, targs, true) - if err != nil { - panic("type parameters should satisfy their own constraints") - } - - tinst, err := types.Instantiate(ctxt, T, targs, true) - if err != nil { - return false - } - - return types.AssignableTo(vinst, tinst) -} diff --git a/internal/typeparams/common_test.go b/internal/typeparams/common_test.go index 779a942d59e..3cbd741360c 100644 --- a/internal/typeparams/common_test.go +++ b/internal/typeparams/common_test.go @@ -204,74 +204,3 @@ func TestFuncOrigin60628(t *testing.T) { } } } - -func TestGenericAssignableTo(t *testing.T) { - tests := []struct { - src string - want bool - }{ - // The inciting issue: golang/go#50887. - {` - type T[P any] interface { - Accept(P) - } - - type V[Q any] struct { - Element Q - } - - func (c V[Q]) Accept(q Q) { c.Element = q } - `, true}, - - // Various permutations on constraints and signatures. - {`type T[P ~int] interface{ A(P) }; type V[Q int] int; func (V[Q]) A(Q) {}`, true}, - {`type T[P int] interface{ A(P) }; type V[Q ~int] int; func (V[Q]) A(Q) {}`, false}, - {`type T[P int|string] interface{ A(P) }; type V[Q int] int; func (V[Q]) A(Q) {}`, true}, - {`type T[P any] interface{ A(P) }; type V[Q any] int; func (V[Q]) A(Q, Q) {}`, false}, - {`type T[P any] interface{ int; A(P) }; type V[Q any] int; func (V[Q]) A(Q) {}`, false}, - - // Various structural restrictions on T. - {`type T[P any] interface{ ~int; A(P) }; type V[Q any] int; func (V[Q]) A(Q) {}`, true}, - {`type T[P any] interface{ ~int|string; A(P) }; type V[Q any] int; func (V[Q]) A(Q) {}`, true}, - {`type T[P any] interface{ int; A(P) }; type V[Q int] int; func (V[Q]) A(Q) {}`, false}, - - // Various recursive constraints. - {`type T[P ~struct{ f *P }] interface{ A(P) }; type V[Q ~struct{ f *Q }] int; func (V[Q]) A(Q) {}`, true}, - {`type T[P ~struct{ f *P }] interface{ A(P) }; type V[Q ~struct{ g *Q }] int; func (V[Q]) A(Q) {}`, false}, - {`type T[P ~*X, X any] interface{ A(P) X }; type V[Q ~*Y, Y any] int; func (V[Q, Y]) A(Q) (y Y) { return }`, true}, - {`type T[P ~*X, X any] interface{ A(P) X }; type V[Q ~**Y, Y any] int; func (V[Q, Y]) A(Q) (y Y) { return }`, false}, - {`type T[P, X any] interface{ A(P) X }; type V[Q ~*Y, Y any] int; func (V[Q, Y]) A(Q) (y Y) { return }`, true}, - {`type T[P ~*X, X any] interface{ A(P) X }; type V[Q, Y any] int; func (V[Q, Y]) A(Q) (y Y) { return }`, false}, - {`type T[P, X any] interface{ A(P) X }; type V[Q, Y any] int; func (V[Q, Y]) A(Q) (y Y) { return }`, true}, - - // In this test case, we reverse the type parameters in the signature of V.A - {`type T[P, X any] interface{ A(P) X }; type V[Q, Y any] int; func (V[Q, Y]) A(Y) (y Q) { return }`, false}, - // It would be nice to return true here: V can only be instantiated with - // [int, int], so the identity of the type parameters should not matter. - {`type T[P, X any] interface{ A(P) X }; type V[Q, Y int] int; func (V[Q, Y]) A(Y) (y Q) { return }`, false}, - } - - for _, test := range tests { - fset := token.NewFileSet() - f, err := parser.ParseFile(fset, "p.go", "package p; "+test.src, 0) - if err != nil { - t.Fatalf("%s:\n%v", test.src, err) - } - var conf types.Config - pkg, err := conf.Check("p", fset, []*ast.File{f}, nil) - if err != nil { - t.Fatalf("%s:\n%v", test.src, err) - } - - V := pkg.Scope().Lookup("V").Type() - T := pkg.Scope().Lookup("T").Type() - - if types.AssignableTo(V, T) { - t.Fatal("AssignableTo") - } - - if got := GenericAssignableTo(nil, V, T); got != test.want { - t.Fatalf("%s:\nGenericAssignableTo(%v, %v) = %v, want %v", test.src, V, T, got, test.want) - } - } -} From c2df0efdb1303d1a25542ce05d50e8008ce53f71 Mon Sep 17 00:00:00 2001 From: Tim King Date: Tue, 3 Dec 2024 12:49:48 -0800 Subject: [PATCH 05/73] internal/gcimporter: remove end-of-section marker from size Remove the bytes for the end-of-section marker "\n$$\n" from the end of the exportdata by descreasing the returned size appropriately. This marker is not a part of the export data so it should be removed. Updates the unified IR reader to not expect the marker to be present. Updates golang/go#70651 Change-Id: Id3324ff21162d80f8e3d1c9792e9157b01ae1a3f Reviewed-on: https://go-review.googlesource.com/c/tools/+/633296 Reviewed-by: Robert Findley Commit-Queue: Tim King LUCI-TryBot-Result: Go LUCI --- internal/gcimporter/exportdata.go | 23 +++++++++++++++++++---- internal/gcimporter/gcimporter_test.go | 1 + internal/gcimporter/ureader_yes.go | 2 -- 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/internal/gcimporter/exportdata.go b/internal/gcimporter/exportdata.go index 6f5d8a21391..6a42d7705ac 100644 --- a/internal/gcimporter/exportdata.go +++ b/internal/gcimporter/exportdata.go @@ -42,7 +42,7 @@ func readGopackHeader(r *bufio.Reader) (name string, size int64, err error) { // export data section of an underlying cmd/compile created archive // file by reading from it. The reader must be positioned at the // start of the file before calling this function. -// The size result is the length of the export data in bytes. +// This returns the length of the export data in bytes. // // This function is needed by [gcexportdata.Read], which must // accept inputs produced by the last two releases of cmd/compile, @@ -62,11 +62,12 @@ func FindExportData(r *bufio.Reader) (size int64, err error) { } // Archive file. Scan to __.PKGDEF. + var arsize int64 var name string - if name, size, err = readGopackHeader(r); err != nil { + if name, arsize, err = readGopackHeader(r); err != nil { return } - arsize := size + size = arsize // First entry should be __.PKGDEF. if name != "__.PKGDEF" { @@ -105,7 +106,21 @@ func FindExportData(r *bufio.Reader) (size int64, err error) { err = fmt.Errorf("unknown export data header: %q", hdr) return } - // TODO(taking): Remove end-of-section marker "\n$$\n" from size. + + // For files with a binary export data header "$$B\n", + // these are always terminated by an end-of-section marker "\n$$\n". + // So the last bytes must always be this constant. + // + // The end-of-section marker is not a part of the export data itself. + // Do not include these in size. + // + // It would be nice to have sanity check that the final bytes after + // the export data are indeed the end-of-section marker. The split + // of gcexportdata.NewReader and gcexportdata.Read make checking this + // ugly so gcimporter gives up enforcing this. The compiler and go/types + // importer do enforce this, which seems good enough. + const endofsection = "\n$$\n" + size -= int64(len(endofsection)) if size < 0 { err = fmt.Errorf("invalid size (%d) in the archive file: %d bytes remain without section headers (recompile package)", arsize, size) diff --git a/internal/gcimporter/gcimporter_test.go b/internal/gcimporter/gcimporter_test.go index 7848f27e0c2..21dcf3d2e84 100644 --- a/internal/gcimporter/gcimporter_test.go +++ b/internal/gcimporter/gcimporter_test.go @@ -327,6 +327,7 @@ func TestVersionHandling(t *testing.T) { t.Fatal(err) } // 2) find export data + // Index is an incorrect but 'good enough for tests' way to find the end of the export data. i := bytes.Index(data, []byte("\n$$B\n")) + 5 j := bytes.Index(data[i:], []byte("\n$$\n")) + i if i < 0 || j < 0 || i > j { diff --git a/internal/gcimporter/ureader_yes.go b/internal/gcimporter/ureader_yes.go index 1db408613c9..9b38187ec2b 100644 --- a/internal/gcimporter/ureader_yes.go +++ b/internal/gcimporter/ureader_yes.go @@ -11,7 +11,6 @@ import ( "go/token" "go/types" "sort" - "strings" "golang.org/x/tools/internal/aliases" "golang.org/x/tools/internal/pkgbits" @@ -71,7 +70,6 @@ func UImportData(fset *token.FileSet, imports map[string]*types.Package, data [] } s := string(data) - s = s[:strings.LastIndex(s, "\n$$\n")] input := pkgbits.NewPkgDecoder(path, s) pkg = readUnifiedPackage(fset, nil, imports, input) return From 7790f2ef15abe929d9e8cb1040bd2bbf0938f556 Mon Sep 17 00:00:00 2001 From: Alan Donovan Date: Fri, 6 Dec 2024 14:16:24 -0500 Subject: [PATCH 06/73] gopls/internal/cache/methodsets: support generics This CL adds support to gopls' index of methodsets to enable matching of generic types and their methods. Previously, the index was based on the notion of "fingerprints", strings that encode the type of a method such that fingerprints are equal if and only if types are equal. Of course this does not work for generic types, which can match multiple distinct types. This change redesigns the fingerprint encoding so that it preserves the tree structure of the type, allowing it to be parsed again later, supporting tree-based matching such as unification. (The encoding is about 30% bigger.) The actual tree matching in this CL is very simple: each type parameter is treated as a wildcard that can match any subtree at all, without regard to consistency of substitution (as in true unification), nor to type parameter constraint satisfaction. Full unification would reduce false positives, but is not urgently needed. This CL addresses only the global algorithm using the methodsets index. See CL 634596 for the local (type-based) algorithm. + Test, relnote, doc Updates golang/go#59224 Change-Id: I670511a8e5ce02ab23ff7b7bd60e7bd7b43c1cca Reviewed-on: https://go-review.googlesource.com/c/tools/+/634197 Auto-Submit: Alan Donovan Reviewed-by: Robert Findley LUCI-TryBot-Result: Go LUCI --- gopls/doc/features/navigation.md | 9 +- gopls/doc/release/v0.18.0.md | 28 ++ .../internal/cache/methodsets/fingerprint.go | 357 ++++++++++++++++++ .../cache/methodsets/fingerprint_test.go | 188 +++++++++ gopls/internal/cache/methodsets/methodsets.go | 267 ++++--------- gopls/internal/golang/implementation.go | 32 +- gopls/internal/golang/references.go | 2 +- .../testdata/implementation/generics.txt | 8 +- gopls/internal/util/frob/frob.go | 5 +- 9 files changed, 684 insertions(+), 212 deletions(-) create mode 100644 gopls/doc/release/v0.18.0.md create mode 100644 gopls/internal/cache/methodsets/fingerprint.go create mode 100644 gopls/internal/cache/methodsets/fingerprint_test.go diff --git a/gopls/doc/features/navigation.md b/gopls/doc/features/navigation.md index 00371341a7c..e2a4954f5c0 100644 --- a/gopls/doc/features/navigation.md +++ b/gopls/doc/features/navigation.md @@ -107,7 +107,14 @@ types with methods due to embedding) may be missing from the results. but that is not consistent with the "scalable" gopls design. --> -Generic types are currently not fully supported; see golang/go#59224. +If either the target type or the candidate type are generic, the +results will include the candidate type if there is any instantiation +of the two types that would allow one to implement the other. +(Note: the matcher doesn't current implement full unification, so type +parameters are treated like wildcards that may match arbitrary +types, without regard to consistency of substitutions across the +method set or even within a single method. +This may lead to occasional spurious matches.) Client support: - **VS Code**: Use [Go to Implementations](https://code.visualstudio.com/docs/editor/editingevolved#_go-to-implementation) (`⌘F12`). diff --git a/gopls/doc/release/v0.18.0.md b/gopls/doc/release/v0.18.0.md new file mode 100644 index 00000000000..f80eeea5929 --- /dev/null +++ b/gopls/doc/release/v0.18.0.md @@ -0,0 +1,28 @@ +# Configuration Changes + +# New features + +## "Implementations" supports generics + +At long last, the "Go to Implementations" feature now fully supports +generic types and functions (#59224). + +For example, invoking the feature on the interface method `Stack.Push` +below will report the concrete method `C[T].Push`, and vice versa. + +```go +package p + +type Stack[T any] interface { + Push(T) error + Pop() (T, bool) +} + +type C[T any] struct{} + +func (C[T]) Push(t T) error { ... } +func (C[T]) Pop() (T, bool) { ... } + +var _ Stack[int] = C[int]{} +``` + diff --git a/gopls/internal/cache/methodsets/fingerprint.go b/gopls/internal/cache/methodsets/fingerprint.go new file mode 100644 index 00000000000..05ccfe0911c --- /dev/null +++ b/gopls/internal/cache/methodsets/fingerprint.go @@ -0,0 +1,357 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. +package methodsets + +import ( + "fmt" + "go/types" + "reflect" + "strconv" + "strings" + "text/scanner" +) + +// Fingerprint syntax +// +// The lexical syntax is essentially Lisp S-expressions: +// +// expr = STRING | INTEGER | IDENT | '(' expr... ')' +// +// where the tokens are as defined by text/scanner. +// +// The grammar of expression forms is: +// +// τ = IDENT -- named or basic type +// | (qual STRING IDENT) -- qualified named type +// | (array INTEGER τ) +// | (slice τ) +// | (ptr τ) +// | (chan IDENT τ) +// | (func τ v? τ) -- signature params, results, variadic? +// | (map τ τ) +// | (struct field*) +// | (tuple τ*) +// | (interface) -- nonempty interface (lossy) +// | (typeparam INTEGER) +// | (inst τ τ...) -- instantiation of a named type +// +// field = IDENT IDENT STRING τ -- name, embedded?, tag, type + +// fingerprint returns an encoding of a [types.Type] such that, in +// most cases, fingerprint(x) == fingerprint(t) iff types.Identical(x, y). +// +// For a minority of types, mostly involving type parameters, identity +// cannot be reduced to string comparison; these types are called +// "tricky", and are indicated by the boolean result. +// +// In general, computing identity correctly for tricky types requires +// the type checker. However, the fingerprint encoding can be parsed +// by [parseFingerprint] into a tree form that permits simple matching +// sufficient to allow a type parameter to unify with any subtree. +// +// In the standard library, 99.8% of package-level types have a +// non-tricky method-set. The most common exceptions are due to type +// parameters. +// +// fingerprint is defined only for the signature types of methods. It +// must not be called for "untyped" basic types, nor the type of a +// generic function. +func fingerprint(t types.Type) (string, bool) { + var buf strings.Builder + tricky := false + var print func(t types.Type) + print = func(t types.Type) { + switch t := t.(type) { + case *types.Alias: + print(types.Unalias(t)) + + case *types.Named: + targs := t.TypeArgs() + if targs != nil { + buf.WriteString("(inst ") + } + tname := t.Obj() + if tname.Pkg() != nil { + fmt.Fprintf(&buf, "(qual %q %s)", tname.Pkg().Path(), tname.Name()) + } else if tname.Name() != "error" && tname.Name() != "comparable" { + panic(tname) // error and comparable the only named types with no package + } else { + buf.WriteString(tname.Name()) + } + if targs != nil { + for i := range targs.Len() { + buf.WriteByte(' ') + print(targs.At(i)) + } + buf.WriteString(")") + } + + case *types.Array: + fmt.Fprintf(&buf, "(array %d ", t.Len()) + print(t.Elem()) + buf.WriteByte(')') + + case *types.Slice: + buf.WriteString("(slice ") + print(t.Elem()) + buf.WriteByte(')') + + case *types.Pointer: + buf.WriteString("(ptr ") + print(t.Elem()) + buf.WriteByte(')') + + case *types.Map: + buf.WriteString("(map ") + print(t.Key()) + buf.WriteByte(' ') + print(t.Elem()) + buf.WriteByte(')') + + case *types.Chan: + fmt.Fprintf(&buf, "(chan %d ", t.Dir()) + print(t.Elem()) + buf.WriteByte(')') + + case *types.Tuple: + buf.WriteString("(tuple") + for i := range t.Len() { + buf.WriteByte(' ') + print(t.At(i).Type()) + } + buf.WriteByte(')') + + case *types.Basic: + // Print byte/uint8 as "byte" instead of calling + // BasicType.String, which prints the two distinctly + // (even though their Kinds are numerically equal). + // Ditto for rune/int32. + switch t.Kind() { + case types.Byte: + buf.WriteString("byte") + case types.Rune: + buf.WriteString("rune") + case types.UnsafePointer: + buf.WriteString(`(qual "unsafe" Pointer)`) + default: + if t.Info()&types.IsUntyped != 0 { + panic("fingerprint of untyped type") + } + buf.WriteString(t.String()) + } + + case *types.Signature: + buf.WriteString("(func ") + print(t.Params()) + if t.Variadic() { + buf.WriteString(" v") + } + buf.WriteByte(' ') + print(t.Results()) + buf.WriteByte(')') + + case *types.Struct: + // Non-empty unnamed struct types in method + // signatures are vanishingly rare. + buf.WriteString("(struct") + for i := range t.NumFields() { + f := t.Field(i) + name := f.Name() + if !f.Exported() { + name = fmt.Sprintf("(qual %q %s)", f.Pkg().Path(), name) + } + + // This isn't quite right for embedded type aliases. + // (See types.TypeString(StructType) and #44410 for context.) + // But this is vanishingly rare. + fmt.Fprintf(&buf, " %s %t %q ", name, f.Embedded(), t.Tag(i)) + print(f.Type()) + } + buf.WriteByte(')') + + case *types.Interface: + if t.NumMethods() == 0 { + buf.WriteString("any") // common case + } else { + // Interface assignability is particularly + // tricky due to the possibility of recursion. + // However, nontrivial interface type literals + // are exceedingly rare in function signatures. + // + // TODO(adonovan): add disambiguating precision + // (e.g. number of methods, their IDs and arities) + // as needs arise (i.e. collisions are observed). + tricky = true + buf.WriteString("(interface)") + } + + case *types.TypeParam: + // Matching of type parameters will require + // parsing fingerprints and unification. + tricky = true + fmt.Fprintf(&buf, "(%s %d)", symTypeparam, t.Index()) + + default: // incl. *types.Union + panic(t) + } + } + + print(t) + + return buf.String(), tricky +} + +const symTypeparam = "typeparam" + +// sexpr defines the representation of a fingerprint tree. +type ( + sexpr any // = string | int | symbol | *cons | nil + symbol string + cons struct{ car, cdr sexpr } +) + +// parseFingerprint returns the type encoded by fp in tree form. +// +// The input must have been produced by [fingerprint] at the same +// source version; parsing is thus infallible. +func parseFingerprint(fp string) sexpr { + var scan scanner.Scanner + scan.Error = func(scan *scanner.Scanner, msg string) { panic(msg) } + scan.Init(strings.NewReader(fp)) + + // next scans a token and updates tok. + var tok rune + next := func() { tok = scan.Scan() } + + next() + + // parse parses a fingerprint and returns its tree. + var parse func() sexpr + parse = func() sexpr { + if tok == '(' { + next() // consume '(' + var head sexpr // empty list + tailcdr := &head + for tok != ')' { + cell := &cons{car: parse()} + *tailcdr = cell + tailcdr = &cell.cdr + } + next() // consume ')' + return head + } + + s := scan.TokenText() + switch tok { + case scanner.Ident: + next() // consume IDENT + return symbol(s) + + case scanner.Int: + next() // consume INT + i, err := strconv.Atoi(s) + if err != nil { + panic(err) + } + return i + + case scanner.String: + next() // consume STRING + s, err := strconv.Unquote(s) + if err != nil { + panic(err) + } + return s + + default: + panic(tok) + } + } + + return parse() +} + +func sexprString(x sexpr) string { + var out strings.Builder + writeSexpr(&out, x) + return out.String() +} + +// writeSexpr formats an S-expression. +// It is provided for debugging. +func writeSexpr(out *strings.Builder, x sexpr) { + switch x := x.(type) { + case nil: + out.WriteString("()") + case string: + fmt.Fprintf(out, "%q", x) + case int: + fmt.Fprintf(out, "%d", x) + case symbol: + out.WriteString(string(x)) + case *cons: + out.WriteString("(") + for { + writeSexpr(out, x.car) + if x.cdr == nil { + break + } else if cdr, ok := x.cdr.(*cons); ok { + x = cdr + out.WriteByte(' ') + } else { + // Dotted list: should never happen, + // but support it for debugging. + out.WriteString(" . ") + print(x.cdr) + break + } + } + out.WriteString(")") + default: + panic(x) + } +} + +// unify reports whether the types of methods x and y match, in the +// presence of type parameters, each of which matches anything at all. +// (It's not true unification as we don't track substitutions.) +// +// TODO(adonovan): implement full unification. +func unify(x, y sexpr) bool { + if isTypeParam(x) >= 0 || isTypeParam(y) >= 0 { + return true // a type parameter matches anything + } + if reflect.TypeOf(x) != reflect.TypeOf(y) { + return false // type mismatch + } + switch x := x.(type) { + case nil, string, int, symbol: + return x == y + case *cons: + y := y.(*cons) + if !unify(x.car, y.car) { + return false + } + if x.cdr == nil { + return y.cdr == nil + } + if y.cdr == nil { + return false + } + return unify(x.cdr, y.cdr) + default: + panic(fmt.Sprintf("unify %T %T", x, y)) + } +} + +// isTypeParam returns the index of the type parameter, +// if x has the form "(typeparam INTEGER)", otherwise -1. +func isTypeParam(x sexpr) int { + if x, ok := x.(*cons); ok { + if sym, ok := x.car.(symbol); ok && sym == symTypeparam { + return 0 + } + } + return -1 +} diff --git a/gopls/internal/cache/methodsets/fingerprint_test.go b/gopls/internal/cache/methodsets/fingerprint_test.go new file mode 100644 index 00000000000..a9f47c1a2e6 --- /dev/null +++ b/gopls/internal/cache/methodsets/fingerprint_test.go @@ -0,0 +1,188 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package methodsets + +// This is an internal test of [fingerprint] and [unify]. +// +// TODO(adonovan): avoid internal tests. +// Break fingerprint.go off into its own package? + +import ( + "go/types" + "testing" + + "golang.org/x/tools/go/packages" + "golang.org/x/tools/go/types/typeutil" + "golang.org/x/tools/internal/testenv" + "golang.org/x/tools/internal/testfiles" + "golang.org/x/tools/txtar" +) + +// Test_fingerprint runs the fingerprint encoder, decoder, and printer +// on the types of all package-level symbols in gopls, and ensures +// that parse+print is lossless. +func Test_fingerprint(t *testing.T) { + if testing.Short() { + t.Skip("skipping slow test") + } + + cfg := &packages.Config{Mode: packages.NeedTypes} + pkgs, err := packages.Load(cfg, "std", "golang.org/x/tools/gopls/...") + if err != nil { + t.Fatal(err) + } + + // Record the fingerprint of each logical type (equivalence + // class of types.Types) and assert that they are all equal. + // (Non-tricky types only.) + var fingerprints typeutil.Map + + type eqclass struct { + class map[types.Type]bool + fp string + } + + for _, pkg := range pkgs { + switch pkg.Types.Path() { + case "unsafe", "builtin": + continue + } + scope := pkg.Types.Scope() + for _, name := range scope.Names() { + obj := scope.Lookup(name) + typ := obj.Type() + + if basic, ok := typ.(*types.Basic); ok && + basic.Info()&types.IsUntyped != 0 { + continue // untyped constant + } + + fp, tricky := fingerprint(typ) // check Type encoder doesn't panic + + // All equivalent (non-tricky) types have the same fingerprint. + if !tricky { + if prevfp, ok := fingerprints.At(typ).(string); !ok { + fingerprints.Set(typ, fp) + } else if fp != prevfp { + t.Errorf("inconsistent fingerprints for type %v:\n- old: %s\n- new: %s", + typ, fp, prevfp) + } + } + + tree := parseFingerprint(fp) // check parser doesn't panic + fp2 := sexprString(tree) // check formatter doesn't pannic + + // A parse+print round-trip should be lossless. + if fp != fp2 { + t.Errorf("%s: %v: parse+print changed fingerprint:\n"+ + "was: %s\ngot: %s\ntype: %v", + pkg.Fset.Position(obj.Pos()), obj, fp, fp2, typ) + } + } + } +} + +// Test_unify exercises the matching algorithm for generic types. +func Test_unify(t *testing.T) { + if testenv.Go1Point() < 24 { + testenv.NeedsGoExperiment(t, "aliastypeparams") // testenv.Go1Point() >= 24 implies aliastypeparams=1 + } + + const src = ` +-- go.mod -- +module example.com +go 1.24 + +-- a/a.go -- +package a + +type Int = int +type String = string + +// Eq.Equal matches casefold.Equal. +type Eq[T any] interface { Equal(T, T) bool } +type casefold struct{} +func (casefold) Equal(x, y string) bool + +// A matches AString. +type A[T any] = struct { x T } +type AString = struct { x string } + +// B matches anything! +type B[T any] = T + +func C1[T any](int, T, ...string) T { panic(0) } +func C2[U any](int, int, ...U) bool { panic(0) } +func C3(int, bool, ...string) rune +func C4(int, bool, ...string) +func C5(int, float64, bool, string) bool + +func DAny[T any](Named[T]) { panic(0) } +func DString(Named[string]) +func DInt(Named[int]) + +type Named[T any] struct { x T } + +func E1(byte) rune +func E2(uint8) int32 +func E3(int8) uint32 +` + pkg := testfiles.LoadPackages(t, txtar.Parse([]byte(src)), "./a")[0] + scope := pkg.Types.Scope() + for _, test := range []struct { + a, b string + method string // optional field or method + want bool + }{ + {"Eq", "casefold", "Equal", true}, + {"A", "AString", "", true}, + {"A", "Eq", "", false}, // completely unrelated + {"B", "String", "", true}, + {"B", "Int", "", true}, + {"B", "A", "", true}, + {"C1", "C2", "", true}, // matches despite inconsistent substitution + {"C1", "C3", "", true}, + {"C1", "C4", "", false}, + {"C1", "C5", "", false}, + {"C2", "C3", "", false}, // intransitive (C1≡C2 ^ C1≡C3) + {"C2", "C4", "", false}, + {"C3", "C4", "", false}, + {"DAny", "DString", "", true}, + {"DAny", "DInt", "", true}, + {"DString", "DInt", "", false}, // different instantiations of Named + {"E1", "E2", "", true}, // byte and rune are just aliases + {"E2", "E3", "", false}, + } { + lookup := func(name string) types.Type { + obj := scope.Lookup(name) + if obj == nil { + t.Fatalf("Lookup %s failed", name) + } + if test.method != "" { + obj, _, _ = types.LookupFieldOrMethod(obj.Type(), true, pkg.Types, test.method) + if obj == nil { + t.Fatalf("Lookup %s.%s failed", name, test.method) + } + } + return obj.Type() + } + + a := lookup(test.a) + b := lookup(test.b) + + afp, _ := fingerprint(a) + bfp, _ := fingerprint(b) + + atree := parseFingerprint(afp) + btree := parseFingerprint(bfp) + + got := unify(atree, btree) + if got != test.want { + t.Errorf("a=%s b=%s method=%s: unify returned %t for these inputs:\n- %s\n- %s", + test.a, test.b, test.method, + got, sexprString(atree), sexprString(btree)) + } + } +} diff --git a/gopls/internal/cache/methodsets/methodsets.go b/gopls/internal/cache/methodsets/methodsets.go index d9173b3b4c3..3026819ee81 100644 --- a/gopls/internal/cache/methodsets/methodsets.go +++ b/gopls/internal/cache/methodsets/methodsets.go @@ -44,12 +44,11 @@ package methodsets // single 64-bit mask is quite effective. See CL 452060 for details. import ( - "fmt" "go/token" "go/types" "hash/crc32" - "strconv" - "strings" + "slices" + "sync/atomic" "golang.org/x/tools/go/types/objectpath" "golang.org/x/tools/gopls/internal/util/bug" @@ -95,7 +94,7 @@ type Location struct { // A Key represents the method set of a given type in a form suitable // to pass to the (*Index).Search method of many different Indexes. type Key struct { - mset gobMethodSet // note: lacks position information + mset *gobMethodSet // note: lacks position information } // KeyOf returns the search key for the method sets of a given type. @@ -123,7 +122,7 @@ type Result struct { // // The result does not include the error.Error method. // TODO(adonovan): give this special case a more systematic treatment. -func (index *Index) Search(key Key, methodID string) []Result { +func (index *Index) Search(key Key, method *types.Func) []Result { var results []Result for _, candidate := range index.pkg.MethodSets { // Traditionally this feature doesn't report @@ -133,30 +132,22 @@ func (index *Index) Search(key Key, methodID string) []Result { if candidate.IsInterface && key.mset.IsInterface { continue } - if !satisfies(candidate, key.mset) && !satisfies(key.mset, candidate) { - continue - } - if candidate.Tricky { - // If any interface method is tricky then extra - // checking may be needed to eliminate a false positive. - // TODO(adonovan): implement it. + if !implements(candidate, key.mset) && !implements(key.mset, candidate) { + continue } - if methodID == "" { + if method == nil { results = append(results, Result{Location: index.location(candidate.Posn)}) } else { for _, m := range candidate.Methods { - // Here we exploit knowledge of the shape of the fingerprint string. - if strings.HasPrefix(m.Fingerprint, methodID) && - m.Fingerprint[len(methodID)] == '(' { - + if m.ID == method.Id() { // Don't report error.Error among the results: // it has no true source location, no package, // and is excluded from the xrefs index. if m.PkgPath == 0 || m.ObjectPath == 0 { - if methodID != "Error" { - panic("missing info for" + methodID) + if m.ID != "Error" { + panic("missing info for" + m.ID) } continue } @@ -174,23 +165,48 @@ func (index *Index) Search(key Key, methodID string) []Result { return results } -// satisfies does a fast check for whether x satisfies y. -func satisfies(x, y gobMethodSet) bool { - return y.IsInterface && x.Mask&y.Mask == y.Mask && subset(y, x) -} +// implements reports whether x implements y. +func implements(x, y *gobMethodSet) bool { + if !y.IsInterface { + return false + } -// subset reports whether method set x is a subset of y. -func subset(x, y gobMethodSet) bool { -outer: - for _, mx := range x.Methods { - for _, my := range y.Methods { - if mx.Sum == my.Sum && mx.Fingerprint == my.Fingerprint { - continue outer // found; try next x method + // Fast path: neither method set is tricky, so all methods can + // be compared by equality of ID and Fingerprint, and the + // entire subset check can be done using the bit mask. + if !x.Tricky && !y.Tricky { + if x.Mask&y.Mask != y.Mask { + return false // x lacks a method of interface y + } + } + + // At least one operand is tricky (e.g. contains a type parameter), + // so we must used tree-based matching (unification). + + // nonmatching reports whether interface method 'my' lacks + // a matching method in set x. (The sense is inverted for use + // with slice.ContainsFunc below.) + nonmatching := func(my *gobMethod) bool { + for _, mx := range x.Methods { + if mx.ID == my.ID { + var match bool + if !mx.Tricky && !my.Tricky { + // Fast path: neither method is tricky, + // so a string match is sufficient. + match = mx.Sum&my.Sum == my.Sum && mx.Fingerprint == my.Fingerprint + } else { + match = unify(mx.parse(), my.parse()) + } + return !match } } - return false // method of x not found in y + return true // method of y not found in x } - return true // all methods of x found in y + + // Each interface method must have a match. + // (This would be more readable with a DeMorganized + // variant of ContainsFunc.) + return !slices.ContainsFunc(y.Methods, nonmatching) } func (index *Index) location(posn gobPosition) Location { @@ -296,7 +312,7 @@ func (b *indexBuilder) string(s string) int { // It calls the optional setIndexInfo function for each gobMethod. // This is used during index construction, but not search (KeyOf), // to store extra information. -func methodSetInfo(t types.Type, setIndexInfo func(*gobMethod, *types.Func)) gobMethodSet { +func methodSetInfo(t types.Type, setIndexInfo func(*gobMethod, *types.Func)) *gobMethodSet { // For non-interface types, use *T // (if T is not already a pointer) // since it may have more methods. @@ -305,21 +321,24 @@ func methodSetInfo(t types.Type, setIndexInfo func(*gobMethod, *types.Func)) gob // Convert the method set into a compact summary. var mask uint64 tricky := false - methods := make([]gobMethod, mset.Len()) + var buf []byte + methods := make([]*gobMethod, mset.Len()) for i := 0; i < mset.Len(); i++ { m := mset.At(i).Obj().(*types.Func) - fp, isTricky := fingerprint(m) + id := m.Id() + fp, isTricky := fingerprint(m.Signature()) if isTricky { tricky = true } - sum := crc32.ChecksumIEEE([]byte(fp)) - methods[i] = gobMethod{Fingerprint: fp, Sum: sum} + buf = append(append(buf[:0], id...), fp...) + sum := crc32.ChecksumIEEE(buf) + methods[i] = &gobMethod{ID: id, Fingerprint: fp, Sum: sum, Tricky: isTricky} if setIndexInfo != nil { - setIndexInfo(&methods[i], m) // set Position, PkgPath, ObjectPath + setIndexInfo(methods[i], m) // set Position, PkgPath, ObjectPath } mask |= 1 << uint64(((sum>>24)^(sum>>16)^(sum>>8)^sum)&0x3f) } - return gobMethodSet{ + return &gobMethodSet{ IsInterface: types.IsInterface(t), Tricky: tricky, Mask: mask, @@ -337,149 +356,6 @@ func EnsurePointer(T types.Type) types.Type { return T } -// fingerprint returns an encoding of a method signature such that two -// methods with equal encodings have identical types, except for a few -// tricky types whose encodings may spuriously match and whose exact -// identity computation requires the type checker to eliminate false -// positives (which are rare). The boolean result indicates whether -// the result was one of these tricky types. -// -// In the standard library, 99.8% of package-level types have a -// non-tricky method-set. The most common exceptions are due to type -// parameters. -// -// The fingerprint string starts with method.Id() + "(". -func fingerprint(method *types.Func) (string, bool) { - var buf strings.Builder - tricky := false - var fprint func(t types.Type) - fprint = func(t types.Type) { - switch t := t.(type) { - case *types.Alias: - fprint(types.Unalias(t)) - - case *types.Named: - tname := t.Obj() - if tname.Pkg() != nil { - buf.WriteString(strconv.Quote(tname.Pkg().Path())) - buf.WriteByte('.') - } else if tname.Name() != "error" && tname.Name() != "comparable" { - panic(tname) // error and comparable the only named types with no package - } - buf.WriteString(tname.Name()) - - case *types.Array: - fmt.Fprintf(&buf, "[%d]", t.Len()) - fprint(t.Elem()) - - case *types.Slice: - buf.WriteString("[]") - fprint(t.Elem()) - - case *types.Pointer: - buf.WriteByte('*') - fprint(t.Elem()) - - case *types.Map: - buf.WriteString("map[") - fprint(t.Key()) - buf.WriteByte(']') - fprint(t.Elem()) - - case *types.Chan: - switch t.Dir() { - case types.SendRecv: - buf.WriteString("chan ") - case types.SendOnly: - buf.WriteString("<-chan ") - case types.RecvOnly: - buf.WriteString("chan<- ") - } - fprint(t.Elem()) - - case *types.Tuple: - buf.WriteByte('(') - for i := 0; i < t.Len(); i++ { - if i > 0 { - buf.WriteByte(',') - } - fprint(t.At(i).Type()) - } - buf.WriteByte(')') - - case *types.Basic: - // Use canonical names for uint8 and int32 aliases. - switch t.Kind() { - case types.Byte: - buf.WriteString("byte") - case types.Rune: - buf.WriteString("rune") - default: - buf.WriteString(t.String()) - } - - case *types.Signature: - buf.WriteString("func") - fprint(t.Params()) - if t.Variadic() { - buf.WriteString("...") // not quite Go syntax - } - fprint(t.Results()) - - case *types.Struct: - // Non-empty unnamed struct types in method - // signatures are vanishingly rare. - buf.WriteString("struct{") - for i := 0; i < t.NumFields(); i++ { - if i > 0 { - buf.WriteByte(';') - } - f := t.Field(i) - // This isn't quite right for embedded type aliases. - // (See types.TypeString(StructType) and #44410 for context.) - // But this is vanishingly rare. - if !f.Embedded() { - buf.WriteString(f.Id()) - buf.WriteByte(' ') - } - fprint(f.Type()) - if tag := t.Tag(i); tag != "" { - buf.WriteByte(' ') - buf.WriteString(strconv.Quote(tag)) - } - } - buf.WriteString("}") - - case *types.Interface: - if t.NumMethods() == 0 { - buf.WriteString("any") // common case - } else { - // Interface assignability is particularly - // tricky due to the possibility of recursion. - tricky = true - // We could still give more disambiguating precision - // than "..." if we wanted to. - buf.WriteString("interface{...}") - } - - case *types.TypeParam: - tricky = true - // TODO(adonovan): refine this by adding a numeric suffix - // indicating the index among the receiver type's parameters. - buf.WriteByte('?') - - default: // incl. *types.Union - panic(t) - } - } - - buf.WriteString(method.Id()) // e.g. "pkg.Type" - sig := method.Signature() - fprint(sig.Params()) - fprint(sig.Results()) - return buf.String(), tricky -} - // -- serial format of index -- // (The name says gob but in fact we use frob.) @@ -488,27 +364,32 @@ var packageCodec = frob.CodecFor[gobPackage]() // A gobPackage records the method set of each package-level type for a single package. type gobPackage struct { Strings []string // index of strings used by gobPosition.File, gobMethod.{Pkg,Object}Path - MethodSets []gobMethodSet + MethodSets []*gobMethodSet } // A gobMethodSet records the method set of a single type. type gobMethodSet struct { Posn gobPosition IsInterface bool - Tricky bool // at least one method is tricky; assignability requires go/types + Tricky bool // at least one method is tricky; fingerprint must be parsed + unified Mask uint64 // mask with 1 bit from each of methods[*].sum - Methods []gobMethod + Methods []*gobMethod } // A gobMethod records the name, type, and position of a single method. type gobMethod struct { - Fingerprint string // string of form "methodID(params...)(results)" - Sum uint32 // checksum of fingerprint + ID string // (*types.Func).Id() value of method + Fingerprint string // encoding of types as string of form "(params)(results)" + Sum uint32 // checksum of ID + fingerprint + Tricky bool // method type contains tricky features (type params, interface types) // index records only (zero in KeyOf; also for index of error.Error). Posn gobPosition // location of method declaration PkgPath int // path of package containing method declaration ObjectPath int // object path of method relative to PkgPath + + // internal fields (not serialized) + tree atomic.Pointer[sexpr] // fingerprint tree, parsed on demand } // A gobPosition records the file, offset, and length of an identifier. @@ -516,3 +397,15 @@ type gobPosition struct { File int // index into gobPackage.Strings Offset, Len int // in bytes } + +// parse returns the method's parsed fingerprint tree. +// It may return a new instance or a cached one. +func (m *gobMethod) parse() sexpr { + ptr := m.tree.Load() + if ptr == nil { + tree := parseFingerprint(m.Fingerprint) + ptr = &tree + m.tree.Store(ptr) // may race; that's ok + } + return *ptr +} diff --git a/gopls/internal/golang/implementation.go b/gopls/internal/golang/implementation.go index b3accff452f..2d504f4dda0 100644 --- a/gopls/internal/golang/implementation.go +++ b/gopls/internal/golang/implementation.go @@ -120,19 +120,19 @@ func implementations(ctx context.Context, snapshot *cache.Snapshot, fh file.Hand // (For methods, report the corresponding method names.) // // This logic is reused for local queries. - typeOrMethod := func(obj types.Object) (types.Type, string) { + typeOrMethod := func(obj types.Object) (types.Type, *types.Func) { switch obj := obj.(type) { case *types.TypeName: - return obj.Type(), "" + return obj.Type(), nil case *types.Func: // For methods, use the receiver type, which may be anonymous. if recv := obj.Signature().Recv(); recv != nil { - return recv.Type(), obj.Id() + return recv.Type(), obj } } - return nil, "" + return nil, nil } - queryType, queryMethodID := typeOrMethod(obj) + queryType, queryMethod := typeOrMethod(obj) if queryType == nil { return nil, bug.Errorf("%s is not a type or method", obj.Name()) // should have been handled by implementsObj } @@ -211,13 +211,13 @@ func implementations(ctx context.Context, snapshot *cache.Snapshot, fh file.Hand if !ok { return ErrNoIdentFound // checked earlier } - // Shadow obj, queryType, and queryMethodID in this package. + // Shadow obj, queryType, and queryMethod in this package. obj := declPkg.TypesInfo().ObjectOf(id) // may be nil - queryType, queryMethodID := typeOrMethod(obj) + queryType, queryMethod := typeOrMethod(obj) if queryType == nil { return fmt.Errorf("querying method sets in package %q: %v", pkgID, err) } - localLocs, err := localImplementations(ctx, snapshot, declPkg, queryType, queryMethodID) + localLocs, err := localImplementations(ctx, snapshot, declPkg, queryType, queryMethod) if err != nil { return fmt.Errorf("querying local implementations %q: %v", pkgID, err) } @@ -231,7 +231,7 @@ func implementations(ctx context.Context, snapshot *cache.Snapshot, fh file.Hand for _, index := range indexes { index := index group.Go(func() error { - for _, res := range index.Search(key, queryMethodID) { + for _, res := range index.Search(key, queryMethod) { loc := res.Location // Map offsets to protocol.Locations in parallel (may involve I/O). group.Go(func() error { @@ -335,14 +335,14 @@ func implementsObj(ctx context.Context, snapshot *cache.Snapshot, uri protocol.D // types that are assignable to/from the query type, and returns a new // unordered array of their locations. // -// If methodID is non-empty, the function instead returns the location +// If method is non-nil, the function instead returns the location // of each type's method (if any) of that ID. // // ("Local" refers to the search within the same package, but this // function's results may include type declarations that are local to // a function body. The global search index excludes such types // because reliably naming such types is hard.) -func localImplementations(ctx context.Context, snapshot *cache.Snapshot, pkg *cache.Package, queryType types.Type, methodID string) ([]protocol.Location, error) { +func localImplementations(ctx context.Context, snapshot *cache.Snapshot, pkg *cache.Package, queryType types.Type, method *types.Func) ([]protocol.Location, error) { queryType = methodsets.EnsurePointer(queryType) // Scan through all type declarations in the syntax. @@ -380,7 +380,7 @@ func localImplementations(ctx context.Context, snapshot *cache.Snapshot, pkg *ca return true } - if methodID == "" { + if method == nil { // Found matching type. locs = append(locs, mustLocation(pgf, spec.Name)) return true @@ -393,13 +393,13 @@ func localImplementations(ctx context.Context, snapshot *cache.Snapshot, pkg *ca // We could recursively search pkg.Imports for it, // but it's easier to walk the method set. for i := 0; i < mset.Len(); i++ { - method := mset.At(i).Obj() - if method.Id() == methodID { - posn := safetoken.StartPosition(pkg.FileSet(), method.Pos()) + m := mset.At(i).Obj() + if m.Id() == method.Id() { + posn := safetoken.StartPosition(pkg.FileSet(), m.Pos()) methodLocs = append(methodLocs, methodsets.Location{ Filename: posn.Filename, Start: posn.Offset, - End: posn.Offset + len(method.Name()), + End: posn.Offset + len(m.Name()), }) break } diff --git a/gopls/internal/golang/references.go b/gopls/internal/golang/references.go index 6679b45df6b..e42ddfa8a06 100644 --- a/gopls/internal/golang/references.go +++ b/gopls/internal/golang/references.go @@ -524,7 +524,7 @@ func expandMethodSearch(ctx context.Context, snapshot *cache.Snapshot, workspace index := index group.Go(func() error { // Consult index for matching methods. - results := index.Search(key, method.Name()) + results := index.Search(key, method) if len(results) == 0 { return nil } diff --git a/gopls/internal/test/marker/testdata/implementation/generics.txt b/gopls/internal/test/marker/testdata/implementation/generics.txt index 4a6c31b22f8..ba5ef3ff2e3 100644 --- a/gopls/internal/test/marker/testdata/implementation/generics.txt +++ b/gopls/internal/test/marker/testdata/implementation/generics.txt @@ -11,20 +11,20 @@ type GenIface[T any] interface { //@loc(GenIface, "GenIface"),implementation("Ge F(int, string, T) //@loc(GenIfaceF, "F"),implementation("F", GCF) } -type GenConc[U any] int //@loc(GenConc, "GenConc"),implementation("GenConc", GI) +type GenConc[U any] int //@loc(GenConc, "GenConc"),implementation("GenConc", GI, GIString) func (GenConc[V]) F(int, string, V) {} //@loc(GenConcF, "F"),implementation("F", GIF) -type GenConcString struct{ GenConc[string] } //@loc(GenConcString, "GenConcString"),implementation(GenConcString, GIString) +type GenConcString struct{ GenConc[string] } //@loc(GenConcString, "GenConcString"),implementation(GenConcString, GIString, GI) -- other/other.go -- package other -type GI[T any] interface { //@loc(GI, "GI"),implementation("GI", GenConc) +type GI[T any] interface { //@loc(GI, "GI"),implementation("GI", GenConc, GenConcString) F(int, string, T) //@loc(GIF, "F"),implementation("F", GenConcF) } -type GIString GI[string] //@loc(GIString, "GIString"),implementation("GIString", GenConcString) +type GIString GI[string] //@loc(GIString, "GIString"),implementation("GIString", GenConcString, GenConc) type GC[U any] int //@loc(GC, "GC"),implementation("GC", GenIface) diff --git a/gopls/internal/util/frob/frob.go b/gopls/internal/util/frob/frob.go index a5fa584215f..c297e2a1014 100644 --- a/gopls/internal/util/frob/frob.go +++ b/gopls/internal/util/frob/frob.go @@ -12,8 +12,7 @@ // - Interface values are not supported; this avoids the need for // the encoding to describe types. // -// - Types that recursively contain private struct fields are not -// permitted. +// - Private struct fields are ignored. // // - The encoding is unspecified and subject to change, so the encoder // and decoder must exactly agree on their implementation and on the @@ -104,7 +103,7 @@ func frobFor(t reflect.Type) *frob { for i := 0; i < fr.t.NumField(); i++ { field := fr.t.Field(i) if field.PkgPath != "" { - panic(fmt.Sprintf("unexported field %v", field)) + continue // skip unexported field } fr.addElem(field.Type) } From 6ebf95a62098ae9400a80b3ec8d8996b16ad0dfa Mon Sep 17 00:00:00 2001 From: Tim King Date: Tue, 3 Dec 2024 15:29:53 -0800 Subject: [PATCH 07/73] internal/gcimporter: remove indexed support from Import Removes support for the indexed binary format from the test only function Import. Additionally makes Import more similar to go/internal/gcimporter.Import by taking a token.FileSet argument. Updates golang/go#70651 Change-Id: I39f120adc554278adb3d114194c27fd5906985a6 Reviewed-on: https://go-review.googlesource.com/c/tools/+/633435 Commit-Queue: Tim King Reviewed-by: Alan Donovan LUCI-TryBot-Result: Go LUCI Reviewed-by: Robert Findley --- internal/gcimporter/gcimporter.go | 11 +++------- internal/gcimporter/gcimporter_test.go | 20 +++++++++--------- .../testdata/versions/test_go1.16_i.a | Bin 3464 -> 0 bytes .../testdata/versions/test_go1.17_i.a | Bin 3638 -> 0 bytes .../testdata/versions/test_go1.18.5_i.a | Bin 4162 -> 0 bytes .../testdata/versions/test_go1.19_i.a | Bin 4026 -> 0 bytes 6 files changed, 13 insertions(+), 18 deletions(-) delete mode 100644 internal/gcimporter/testdata/versions/test_go1.16_i.a delete mode 100644 internal/gcimporter/testdata/versions/test_go1.17_i.a delete mode 100644 internal/gcimporter/testdata/versions/test_go1.18.5_i.a delete mode 100644 internal/gcimporter/testdata/versions/test_go1.19_i.a diff --git a/internal/gcimporter/gcimporter.go b/internal/gcimporter/gcimporter.go index dbbca860432..c1136ea6439 100644 --- a/internal/gcimporter/gcimporter.go +++ b/internal/gcimporter/gcimporter.go @@ -162,8 +162,8 @@ func FindPkg(path, srcDir string) (filename, id string) { // the corresponding package object to the packages map, and returns the object. // The packages map must contain all packages already imported. // -// TODO(taking): Import is only used in tests. Move to gcimporter_test. -func Import(packages map[string]*types.Package, path, srcDir string, lookup func(path string) (io.ReadCloser, error)) (pkg *types.Package, err error) { +// Import is only used in tests. +func Import(fset *token.FileSet, packages map[string]*types.Package, path, srcDir string, lookup func(path string) (io.ReadCloser, error)) (pkg *types.Package, err error) { var rc io.ReadCloser var filename, id string if lookup != nil { @@ -227,10 +227,6 @@ func Import(packages map[string]*types.Package, path, srcDir string, lookup func return nil, fmt.Errorf("no data to load a package from for path %s", id) } - // TODO(gri): allow clients of go/importer to provide a FileSet. - // Or, define a new standard go/types/gcexportdata package. - fset := token.NewFileSet() - // Select appropriate importer. switch data[0] { case 'v', 'c', 'd': @@ -241,8 +237,7 @@ func Import(packages map[string]*types.Package, path, srcDir string, lookup func // indexed: emitted by cmd/compile till go1.19; // now used only for serializing go/types. // See https://github.com/golang/go/issues/69491. - _, pkg, err := IImportData(fset, packages, data[1:], id) - return pkg, err + return nil, fmt.Errorf("indexed (i) import format is no longer supported") case 'u': // unified: emitted by cmd/compile since go1.20. diff --git a/internal/gcimporter/gcimporter_test.go b/internal/gcimporter/gcimporter_test.go index 21dcf3d2e84..20fbdef7f93 100644 --- a/internal/gcimporter/gcimporter_test.go +++ b/internal/gcimporter/gcimporter_test.go @@ -93,7 +93,7 @@ func compilePkg(t *testing.T, dirname, filename, outdirname string, packagefiles func testPath(t *testing.T, path, srcDir string) *types.Package { t0 := time.Now() - pkg, err := gcimporter.Import(make(map[string]*types.Package), path, srcDir, nil) + pkg, err := gcimporter.Import(token.NewFileSet(), make(map[string]*types.Package), path, srcDir, nil) if err != nil { t.Errorf("testPath(%s): %s", path, err) return nil @@ -314,7 +314,7 @@ func TestVersionHandling(t *testing.T) { } // test that export data can be imported - _, err := gcimporter.Import(make(map[string]*types.Package), pkgpath, dir, nil) + _, err := gcimporter.Import(token.NewFileSet(), make(map[string]*types.Package), pkgpath, dir, nil) if err != nil { t.Errorf("import %q failed: %v", pkgpath, err) continue @@ -343,7 +343,7 @@ func TestVersionHandling(t *testing.T) { os.WriteFile(filename, data, 0666) // test that importing the corrupted file results in an error - _, err = gcimporter.Import(make(map[string]*types.Package), pkgpath, corruptdir, nil) + _, err = gcimporter.Import(token.NewFileSet(), make(map[string]*types.Package), pkgpath, corruptdir, nil) if err == nil { t.Errorf("import corrupted %q succeeded", pkgpath) } else if msg := err.Error(); !strings.Contains(msg, "internal error") { @@ -473,7 +473,7 @@ func importObject(t *testing.T, name string) types.Object { importPath := s[0] objName := s[1] - pkg, err := gcimporter.Import(make(map[string]*types.Package), importPath, ".", nil) + pkg, err := gcimporter.Import(token.NewFileSet(), make(map[string]*types.Package), importPath, ".", nil) if err != nil { t.Error(err) return nil @@ -555,7 +555,7 @@ func TestCorrectMethodPackage(t *testing.T) { testenv.NeedsGoBuild(t) // to find stdlib export data in the build cache imports := make(map[string]*types.Package) - _, err := gcimporter.Import(imports, "net/http", ".", nil) + _, err := gcimporter.Import(token.NewFileSet(), imports, "net/http", ".", nil) if err != nil { t.Fatal(err) } @@ -613,7 +613,7 @@ func TestIssue13898(t *testing.T) { // import go/internal/gcimporter which imports go/types partially imports := make(map[string]*types.Package) - _, err := gcimporter.Import(imports, "go/internal/gcimporter", ".", nil) + _, err := gcimporter.Import(token.NewFileSet(), imports, "go/internal/gcimporter", ".", nil) if err != nil { t.Fatal(err) } @@ -673,7 +673,7 @@ func TestIssue15517(t *testing.T) { // The same issue occurs with vendoring.) imports := make(map[string]*types.Package) for i := 0; i < 3; i++ { - if _, err := gcimporter.Import(imports, "./././testdata/p", tmpdir, nil); err != nil { + if _, err := gcimporter.Import(token.NewFileSet(), imports, "./././testdata/p", tmpdir, nil); err != nil { t.Fatal(err) } } @@ -909,7 +909,7 @@ func TestIssue58296(t *testing.T) { } // make sure a and b are both imported by c. - pkg, err := gcimporter.Import(imports, "./c", testoutdir, nil) + pkg, err := gcimporter.Import(token.NewFileSet(), imports, "./c", testoutdir, nil) if err != nil { t.Fatal(err) } @@ -952,7 +952,7 @@ func TestIssueAliases(t *testing.T) { ) // import c from gc export data using a and b. - pkg, err := gcimporter.Import(map[string]*types.Package{ + pkg, err := gcimporter.Import(token.NewFileSet(), map[string]*types.Package{ apkg: types.NewPackage(apkg, "a"), bpkg: types.NewPackage(bpkg, "b"), }, "./c", testoutdir, nil) @@ -996,7 +996,7 @@ func apkg(testoutdir string) string { } func importPkg(t *testing.T, path, srcDir string) *types.Package { - pkg, err := gcimporter.Import(make(map[string]*types.Package), path, srcDir, nil) + pkg, err := gcimporter.Import(token.NewFileSet(), make(map[string]*types.Package), path, srcDir, nil) if err != nil { t.Fatal(err) } diff --git a/internal/gcimporter/testdata/versions/test_go1.16_i.a b/internal/gcimporter/testdata/versions/test_go1.16_i.a deleted file mode 100644 index 35dc863e81cd71c58097b9fc92906fb095aed57e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3464 zcmcImZ)_A*5TCs}=yk!g1Oz2U4y*{o?%lQ5yDMo3!O|imwFsyo#OvPfyY0f>?sadk z1=L^y#xMRs4EhCQNK7@T3E~Hkh!Tluh(^GOf+Pfi4;uers)>qPXLj%1U9YVW)JcCk zv%fcQ-n@Blt~<9)ww2x`Vk#wduj*`H-VyR7Qq-h8+k3`iF?Xb?skP|QBWhO2N^fL} z6H;Y+KsQ6OosGvrniZ9z@z6tyP0M6rx}Y0sNLNGiwq!e2tXw@1>s@1XYsSVFZMB*T zXF9uE*BM*>QAFo2q(MXFo`u|pFt_WD^ zXxvbFx&i=P@_G0c^;N_cKfobGPXORJV#6d44(b_`0dk0s5esSnx@rMlLmZp}aI6St zOO8nr>ys2Ko0Uzq(a=rSDBD^g%SO*59rv?LQTC9PQ*-N5zRq%w)Fg zFm;KW)S_!=WJ$W2v83zwOykYcEJ-Q2^K!PO78GVru=ff_S=l6GdUBRc;LFRHDVWLx zE)}J;3#OxEtSrm2zJhGTV^~y9YD!wi!bhTBOj?w#ytIh!cSR7%9lvIgs=|6fy@txk zPVYp?RxO@tbP4Q9eq{eq-{O^45wj2%5nYJgc&+b3{Tl+iwjGkL0Wcr+92`V)DQqTP z6p8?Vx4%+6vJRl3g7wIHfSbpVA^l{d*hD`?`ysIVd1k^_nbTb%k9s;k~s4I@(WWE zgHN37JGF3U+n~~LxNWvAym{iD&Ryz*XZkN~cxnHnv!Cob)wb2x*nVjESYh9j#~&Xm z{66i@!B0E9#LK7;AU;BzK%7CGL!3u^g}99P8F3Xs{Z)g9T8E(g%a?UzWMmsvC^U-w z3t1%GHVokZMo<1MvJreON4SbCh94&`HC||xFU2KaYVWaesc*_G#YF-}uSPjg1gcI@ z=)orm&!HRbas=+%gKRlpV(EVOj$)|}_)p%I;9{x>39+Q9DT0gZ$wc8S?XMh{v#tvj z*%iJE+S^#)n9EkjU4d+1jyoM66qkP2?jM)kFovbO)H8~u9Qcpm*c{NcBC|ixcLy>8 zO`Vg%4(Aj5qx16)5Myh`VO^~350D;5@M6`(}KB(u+o!d^A-Ovc0>^Jw^ z@1A?^x#!+9nVs4pTGGH=K9v&smUJ&z*cG4>a$0PtPM`5u%pQqE+8m31UNr+|dbJ`I z0O9j)^^Fhrh zD3&4WK~>VSIn#n(u%P4%vRDv<>tK#%8hM)$RFiNi9}e=Jx@fHF(iB~;?dJG(WD!TY zS4^=;fDjMix)&2}DNi=_uJ;aJOzedyc#=}hlwdj=9?1ypSISN_lB8)0{D0Y*$`i6pgfJ!WlWOD2Ys4G7*!~<6m8XkTfG>hE+48 z=@4>b9xQMDLNJV>I&NAhqq*ztbiY ziTg%jM^1aLCk_5T9UKO~d^AGp6kd*GV>yXY9JE8CM)tGZi1kxlFK?gy^aqp99{%Io zbg1L*A3Jv(ncMKvs~4Ua>f3&4{*#_aq=R7Z*|M<7T9__`$rFT|sC8tkL zI`QQ*Uv+HIS1;H<^l9;}Enhr3Sp2glx_Gksob}3MsqfxA-ul_;`{op-raO0jedPFt z_a84DJTv3Np^KZ3{qRvG?%~|>`g6`!#r=<(He^9pa9j zz2nI}gXs&nAs=9Z1H8U?oS62j0bbq7fNLo?fU|O-|0G}!052WB4dw%O0$z8p8`@6* zCjj3A&H;V`TmW1I3;})z)WAVc05kz61F*L35`-~FhKGkYVY&hU*B!qEG+JQw`bNv{ zABPT&wf+lebXmbSR%c+WHTp*DD!#G2TuY*FU`W}L7zsMTR6*SR(4eqL`apL%Vb=XX zyLO2*q=XENr7-1j1& zeYaB&@I>GEf?QUae&}+V2daN8jger?C>0_pqyGl$&A@Ru4Rhwf6Kb62qtWMY6#s5lwyuoFhKxZF3nGrW-F9u)b!0rYunh#afsBj^Yj1CFy9@2T-EBLD z5A+Y;Pka*|B7|r}rqSSo5Tou7P!nS`;u29515pGu0TYP8<~iT(*LAgH_#pAie&0Fw zch7gebI$kJ?euCv6FcUaJs!S!NyEardW+s{35i12_1@)l(l>TDv+$aOszvp!mBg6E zFKFv!#Uf~7m(vnd%lUFQUtwvTqe(%*C$HCpNJP@2rTU{L1wt?=@g!)$XlXngN0-j8?L5~$_j^W{lBZ}bTfhFEO&?eEJ2PCvM5VOGe|FB zAK)lp@+d;~0_JDyXaadh4k5n*L=z!>fZIkBlAT9L(O4a^4H1dwrA}T{!(l=3mxW|S zDuZ1S4@*ic$`4jQD27x~2t|9}3D|9wc2{M<7pQUxw(?51W{k3FD-c)2 zVN6mMLyIdh8GMDMu+$kBLM|sP3nPP~PllzlC2EC};t}IZ@I0kB;pvJCk`x#l3ATjr2jAX-0fP5GL8F3^R1lAhKeE>?r%|uBD z$fJOP0kDLtj85bNnGZ8k5`E+BINVEhE|} zW5ZRQ#DZt!jX88}+-by?U3NKav(O$wd+Xr*eeZu`s9!w`O z-ZL~L-H7(-Mtv_%JJ<3>Q;%}{Ch-d-&a|6NcwbzklY? zE%NgRyL;LmBFByvop98tQ)~C{$=&1AcejFG$cw|hSJP> zl}=+;*qF5s^b{v(jRzfDh5*xOOy_>!a3RyIv6_~lbU#=FL$7cn?rptY0veaV21s)P zpOTq;W34Sit~prA1hr^a}Gv(OI~!pK%K8x9 e*~aXd(UQY?lX6U^$?SO~$pUAR*%vcqIQb`-wP0ZY diff --git a/internal/gcimporter/testdata/versions/test_go1.19_i.a b/internal/gcimporter/testdata/versions/test_go1.19_i.a deleted file mode 100644 index ff8f5995bb8b676af52b79f36e7339f6aaca6a3c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4026 zcmc&%Yitx%6uz@v+U%~=4Mmk2wKhYF#}y+n*X&A$ns7I!v`ARg!s7IxvA)*l z^BK(*4gNr~r2fgOB`sdLG8)aVl$+I5LCtEpsHJt;n#gpw+dXq;xm$BIOsMqMClJvq zh4q-;D#g5Q-u9%fM-pBwmQ;+G6!EIE7LDr$dfudxNQR}PUdsB%Box+^wuiV1boW3??$>U_xLC6PCHzd)2dl%001PL>BI#ss}y#K&h;n z3|F;6e^IgN{XbM~nww!sd?k^vH3fk&R+3?25p*4J9=HJ%j$&*t;LSAg1nDfs4gix~ zjCBHIM>F;baPB@6$&R?<@hB}GS&v4gSU5kT#gu%kbSkREk_pd1eN{Q4%Tgq9>Uf1z z*VdkBD4yT3*4JEBv#Oywy3VhbmJWER^U9Tur&DvdlPTTDgIvE(8DQN zF@~v@%~6uqNf=j(D#Q3%^J+@P)h)bS>ZROLnuOI6TU zD;kszjVF&zh%JbRk2&((x!Z^pN*mprk9l(AQnGotIhlU_IR^7Nfx~Yt*?)9F;GBej z$v`muAGcLIEWm}Smhvc9Ags)k#5GC z`^fRoQ_X=g7#(E-BC*{T3AMF^fe)_c?tvcok=St5ijJM z$qOt89hy-taL^*DbVSnJFIuGa`gD-n0L(~W*-)r8Bl3NKlen`uSqOa|=HYX>qkqmF@UPbVph9-flv#M^YI&Rx^u;?isTPU3gD%JXqgdS861<&lPS zJ;ld%A9*7n*B+hn+vTtJeQ-$D3E* zGHdy{4ch|We{-nxS>r;!=EwZ^NC$^vlkvN-E~Ocd2{uO z>|d{~cx}t2E5QdYj#_zzia=Wkziu3~>1bC@xd(U{F{c9eS|(tCbkLY|dmRC~fcvrV zGk{Lu)jn*8ejV5W>`KEP^l9gx1-gKX0B!oKz<0pUz%9Um4dw=N06NIF!_(W_yO~NY z-cH{F4Tj$VPCBQi{0W+NYyn^k@;hh@GgNH4BE;LhDK>fM0z<^6<7}fTHkkAwDHFPB zsC>vI1Vv(HQU}`>#PSY-w&hJU?sa~#+i0o@KCZa9%KHqup3F?B0HCRABo;jb_7L84 zOxn7^EN`ky+K^yOnPeI44-9!7J*vqN-bE&z9=i&(^HDUW(2!KBYQRQQsVIy3+iA;t ztx40YNG$J0*tQ_#O%)^=BEdA8rn42CK+&)lnS)k{H1NUQ|IPBQblaMeui-J|`#Wk6+=)=!4g55L#Nq(YPGL*1r{;y1*-X?Er{{m!vGSUD5 From 2cc4218b61bdfdb0a16316fa3631ba371388da0f Mon Sep 17 00:00:00 2001 From: Tim King Date: Wed, 4 Dec 2024 10:06:08 -0800 Subject: [PATCH 08/73] internal/gcimporter: moving byPath code location Moves the location of byPath. This minimizes the difference with $GOROOT/src/go/internal/gcimporter/gcimporter.go . Change-Id: I6f6e0fe0f824781939346e0014a5604fde9e5c2e Reviewed-on: https://go-review.googlesource.com/c/tools/+/633656 LUCI-TryBot-Result: Go LUCI Reviewed-by: Robert Findley Commit-Queue: Tim King --- internal/gcimporter/gcimporter.go | 6 ------ internal/gcimporter/iimport.go | 6 ++++++ 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/internal/gcimporter/gcimporter.go b/internal/gcimporter/gcimporter.go index c1136ea6439..e2ad35139f3 100644 --- a/internal/gcimporter/gcimporter.go +++ b/internal/gcimporter/gcimporter.go @@ -252,9 +252,3 @@ func Import(fset *token.FileSet, packages map[string]*types.Package, path, srcDi return nil, fmt.Errorf("unexpected export data with prefix %q for path %s", string(data[:l]), id) } } - -type byPath []*types.Package - -func (a byPath) Len() int { return len(a) } -func (a byPath) Swap(i, j int) { a[i], a[j] = a[j], a[i] } -func (a byPath) Less(i, j int) bool { return a[i].Path() < a[j].Path() } diff --git a/internal/gcimporter/iimport.go b/internal/gcimporter/iimport.go index e260c0e8dbf..7f8794e8625 100644 --- a/internal/gcimporter/iimport.go +++ b/internal/gcimporter/iimport.go @@ -1111,3 +1111,9 @@ func (r *importReader) byte() byte { } return x } + +type byPath []*types.Package + +func (a byPath) Len() int { return len(a) } +func (a byPath) Swap(i, j int) { a[i], a[j] = a[j], a[i] } +func (a byPath) Less(i, j int) bool { return a[i].Path() < a[j].Path() } From 3689f0b08f1d6855e488550978f33e9fa1dd9614 Mon Sep 17 00:00:00 2001 From: Tim King Date: Wed, 4 Dec 2024 10:18:38 -0800 Subject: [PATCH 09/73] internal/gcimporter: reuse archive.ReadHeader implementation Adds an internal copy of the GOROOT/src/cmd/internal/archive.ReadHeader to x/tools. This replaces readGopackHeader. The result is one less different implementation of reading an archive header between GOROOT and x/tools. Updates golang/go#70651 Change-Id: I69028da6472d6b06a613b554158301894326e735 Reviewed-on: https://go-review.googlesource.com/c/tools/+/633657 Commit-Queue: Tim King LUCI-TryBot-Result: Go LUCI Reviewed-by: Robert Findley --- internal/gcimporter/exportdata.go | 40 ++++--------------------------- internal/gcimporter/support.go | 30 +++++++++++++++++++++++ 2 files changed, 35 insertions(+), 35 deletions(-) create mode 100644 internal/gcimporter/support.go diff --git a/internal/gcimporter/exportdata.go b/internal/gcimporter/exportdata.go index 6a42d7705ac..7745236b392 100644 --- a/internal/gcimporter/exportdata.go +++ b/internal/gcimporter/exportdata.go @@ -11,33 +11,9 @@ package gcimporter import ( "bufio" "fmt" - "io" - "strconv" "strings" ) -func readGopackHeader(r *bufio.Reader) (name string, size int64, err error) { - // See $GOROOT/include/ar.h. - hdr := make([]byte, 16+12+6+6+8+10+2) - _, err = io.ReadFull(r, hdr) - if err != nil { - return - } - // leave for debugging - if false { - fmt.Printf("header: %s", hdr) - } - s := strings.TrimSpace(string(hdr[16+12+6+6+8:][:10])) - length, err := strconv.Atoi(s) - size = int64(length) - if err != nil || hdr[len(hdr)-2] != '`' || hdr[len(hdr)-1] != '\n' { - err = fmt.Errorf("invalid archive header") - return - } - name = strings.TrimSpace(string(hdr[:16])) - return -} - // FindExportData positions the reader r at the beginning of the // export data section of an underlying cmd/compile created archive // file by reading from it. The reader must be positioned at the @@ -61,19 +37,13 @@ func FindExportData(r *bufio.Reader) (size int64, err error) { return } - // Archive file. Scan to __.PKGDEF. - var arsize int64 - var name string - if name, arsize, err = readGopackHeader(r); err != nil { - return - } - size = arsize - - // First entry should be __.PKGDEF. - if name != "__.PKGDEF" { - err = fmt.Errorf("go archive is missing __.PKGDEF") + // Archive file with the first file being __.PKGDEF. + arsize := readArchiveHeader(r, "__.PKGDEF") + if arsize <= 0 { + err = fmt.Errorf("not a package file") return } + size = int64(arsize) // Read first line of __.PKGDEF data, so that line // is once again the first line of the input. diff --git a/internal/gcimporter/support.go b/internal/gcimporter/support.go new file mode 100644 index 00000000000..4af810dc412 --- /dev/null +++ b/internal/gcimporter/support.go @@ -0,0 +1,30 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package gcimporter + +import ( + "bufio" + "io" + "strconv" + "strings" +) + +// Copy of $GOROOT/src/cmd/internal/archive.ReadHeader. +func readArchiveHeader(b *bufio.Reader, name string) int { + // architecture-independent object file output + const HeaderSize = 60 + + var buf [HeaderSize]byte + if _, err := io.ReadFull(b, buf[:]); err != nil { + return -1 + } + aname := strings.Trim(string(buf[0:16]), " ") + if !strings.HasPrefix(aname, name) { + return -1 + } + asize := strings.Trim(string(buf[48:58]), " ") + i, _ := strconv.Atoi(asize) + return i +} From 5e47a3db26e485cc95099a43408f41c65daebb6a Mon Sep 17 00:00:00 2001 From: Rob Findley Date: Tue, 26 Nov 2024 18:21:36 +0000 Subject: [PATCH 10/73] gopls/internal/cache: move Snapshot.Symbols to symbols.go Pure code movement; no behavior change. Change-Id: I7ef073d58355f9ac8853cc0c1e33e0af3f9a391f Reviewed-on: https://go-review.googlesource.com/c/tools/+/634795 Reviewed-by: Peter Weinberger LUCI-TryBot-Result: Go LUCI --- gopls/internal/cache/snapshot.go | 62 ------------------------------- gopls/internal/cache/symbols.go | 64 ++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+), 62 deletions(-) diff --git a/gopls/internal/cache/snapshot.go b/gopls/internal/cache/snapshot.go index 46b0a6a1b5c..50fb01ce532 100644 --- a/gopls/internal/cache/snapshot.go +++ b/gopls/internal/cache/snapshot.go @@ -17,14 +17,12 @@ import ( "path" "path/filepath" "regexp" - "runtime" "slices" "sort" "strconv" "strings" "sync" - "golang.org/x/sync/errgroup" "golang.org/x/tools/go/types/objectpath" "golang.org/x/tools/gopls/internal/cache/metadata" "golang.org/x/tools/gopls/internal/cache/methodsets" @@ -961,66 +959,6 @@ func (s *Snapshot) isWorkspacePackage(id PackageID) bool { return ok } -// Symbols extracts and returns symbol information for every file contained in -// a loaded package. It awaits snapshot loading. -// -// If workspaceOnly is set, this only includes symbols from files in a -// workspace package. Otherwise, it returns symbols from all loaded packages. -// -// TODO(rfindley): move to symbols.go. -func (s *Snapshot) Symbols(ctx context.Context, workspaceOnly bool) (map[protocol.DocumentURI][]Symbol, error) { - var ( - meta []*metadata.Package - err error - ) - if workspaceOnly { - meta, err = s.WorkspaceMetadata(ctx) - } else { - meta, err = s.AllMetadata(ctx) - } - if err != nil { - return nil, fmt.Errorf("loading metadata: %v", err) - } - - goFiles := make(map[protocol.DocumentURI]struct{}) - for _, mp := range meta { - for _, uri := range mp.GoFiles { - goFiles[uri] = struct{}{} - } - for _, uri := range mp.CompiledGoFiles { - goFiles[uri] = struct{}{} - } - } - - // Symbolize them in parallel. - var ( - group errgroup.Group - nprocs = 2 * runtime.GOMAXPROCS(-1) // symbolize is a mix of I/O and CPU - resultMu sync.Mutex - result = make(map[protocol.DocumentURI][]Symbol) - ) - group.SetLimit(nprocs) - for uri := range goFiles { - uri := uri - group.Go(func() error { - symbols, err := s.symbolize(ctx, uri) - if err != nil { - return err - } - resultMu.Lock() - result[uri] = symbols - resultMu.Unlock() - return nil - }) - } - // Keep going on errors, but log the first failure. - // Partial results are better than no symbol results. - if err := group.Wait(); err != nil { - event.Error(ctx, "getting snapshot symbols", err) - } - return result, nil -} - // AllMetadata returns a new unordered array of metadata for // all packages known to this snapshot, which includes the // packages of all workspace modules plus their transitive diff --git a/gopls/internal/cache/symbols.go b/gopls/internal/cache/symbols.go index 9954c747798..e409c9cc6aa 100644 --- a/gopls/internal/cache/symbols.go +++ b/gopls/internal/cache/symbols.go @@ -6,15 +6,21 @@ package cache import ( "context" + "fmt" "go/ast" "go/token" "go/types" + "runtime" "strings" + "sync" + "golang.org/x/sync/errgroup" + "golang.org/x/tools/gopls/internal/cache/metadata" "golang.org/x/tools/gopls/internal/cache/parsego" "golang.org/x/tools/gopls/internal/file" "golang.org/x/tools/gopls/internal/protocol" "golang.org/x/tools/gopls/internal/util/astutil" + "golang.org/x/tools/internal/event" ) // Symbol holds a precomputed symbol value. Note: we avoid using the @@ -26,6 +32,64 @@ type Symbol struct { Range protocol.Range } +// Symbols extracts and returns symbol information for every file contained in +// a loaded package. It awaits snapshot loading. +// +// If workspaceOnly is set, this only includes symbols from files in a +// workspace package. Otherwise, it returns symbols from all loaded packages. +func (s *Snapshot) Symbols(ctx context.Context, workspaceOnly bool) (map[protocol.DocumentURI][]Symbol, error) { + var ( + meta []*metadata.Package + err error + ) + if workspaceOnly { + meta, err = s.WorkspaceMetadata(ctx) + } else { + meta, err = s.AllMetadata(ctx) + } + if err != nil { + return nil, fmt.Errorf("loading metadata: %v", err) + } + + goFiles := make(map[protocol.DocumentURI]struct{}) + for _, mp := range meta { + for _, uri := range mp.GoFiles { + goFiles[uri] = struct{}{} + } + for _, uri := range mp.CompiledGoFiles { + goFiles[uri] = struct{}{} + } + } + + // Symbolize them in parallel. + var ( + group errgroup.Group + nprocs = 2 * runtime.GOMAXPROCS(-1) // symbolize is a mix of I/O and CPU + resultMu sync.Mutex + result = make(map[protocol.DocumentURI][]Symbol) + ) + group.SetLimit(nprocs) + for uri := range goFiles { + uri := uri + group.Go(func() error { + symbols, err := s.symbolize(ctx, uri) + if err != nil { + return err + } + resultMu.Lock() + result[uri] = symbols + resultMu.Unlock() + return nil + }) + } + // Keep going on errors, but log the first failure. + // Partial results are better than no symbol results. + if err := group.Wait(); err != nil { + event.Error(ctx, "getting snapshot symbols", err) + } + return result, nil +} + // symbolize returns the result of symbolizing the file identified by uri, using a cache. func (s *Snapshot) symbolize(ctx context.Context, uri protocol.DocumentURI) ([]Symbol, error) { From ab5f969ba9947dd46c33ddb30f5836e6d3fcffec Mon Sep 17 00:00:00 2001 From: Tim King Date: Mon, 9 Dec 2024 16:10:42 -0800 Subject: [PATCH 11/73] internal/gcimporter: moving FindPkg Moves FindPkg and related variables from gcimporter.go to exportdata.go. This is a part of minimizing the code delta between internal/gcimporter and GOROOT/src/internal/exportdata. Updates golang/go#70651 Change-Id: I74d2ba2085d0b034ff9f98f7cae17a2231ae3a6c Reviewed-on: https://go-review.googlesource.com/c/tools/+/634518 LUCI-TryBot-Result: Go LUCI Reviewed-by: Robert Findley Commit-Queue: Tim King --- internal/gcimporter/exportdata.go | 119 ++++++++++++++++++++++++++++++ internal/gcimporter/gcimporter.go | 119 ------------------------------ 2 files changed, 119 insertions(+), 119 deletions(-) diff --git a/internal/gcimporter/exportdata.go b/internal/gcimporter/exportdata.go index 7745236b392..6962b61af00 100644 --- a/internal/gcimporter/exportdata.go +++ b/internal/gcimporter/exportdata.go @@ -10,8 +10,14 @@ package gcimporter import ( "bufio" + "bytes" "fmt" + "go/build" + "os" + "os/exec" + "path/filepath" "strings" + "sync" ) // FindExportData positions the reader r at the beginning of the @@ -99,3 +105,116 @@ func FindExportData(r *bufio.Reader) (size int64, err error) { return } + +// FindPkg returns the filename and unique package id for an import +// path based on package information provided by build.Import (using +// the build.Default build.Context). A relative srcDir is interpreted +// relative to the current working directory. +// If no file was found, an empty filename is returned. +func FindPkg(path, srcDir string) (filename, id string) { + if path == "" { + return + } + + var noext string + switch { + default: + // "x" -> "$GOPATH/pkg/$GOOS_$GOARCH/x.ext", "x" + // Don't require the source files to be present. + if abs, err := filepath.Abs(srcDir); err == nil { // see issue 14282 + srcDir = abs + } + bp, _ := build.Import(path, srcDir, build.FindOnly|build.AllowBinary) + if bp.PkgObj == "" { + var ok bool + if bp.Goroot && bp.Dir != "" { + filename, ok = lookupGorootExport(bp.Dir) + } + if !ok { + id = path // make sure we have an id to print in error message + return + } + } else { + noext = strings.TrimSuffix(bp.PkgObj, ".a") + id = bp.ImportPath + } + + case build.IsLocalImport(path): + // "./x" -> "/this/directory/x.ext", "/this/directory/x" + noext = filepath.Join(srcDir, path) + id = noext + + case filepath.IsAbs(path): + // for completeness only - go/build.Import + // does not support absolute imports + // "/x" -> "/x.ext", "/x" + noext = path + id = path + } + + if false { // for debugging + if path != id { + fmt.Printf("%s -> %s\n", path, id) + } + } + + if filename != "" { + if f, err := os.Stat(filename); err == nil && !f.IsDir() { + return + } + } + + // try extensions + for _, ext := range pkgExts { + filename = noext + ext + if f, err := os.Stat(filename); err == nil && !f.IsDir() { + return + } + } + + filename = "" // not found + return +} + +var pkgExts = [...]string{".a", ".o"} + +var exportMap sync.Map // package dir → func() (string, bool) + +// lookupGorootExport returns the location of the export data +// (normally found in the build cache, but located in GOROOT/pkg +// in prior Go releases) for the package located in pkgDir. +// +// (We use the package's directory instead of its import path +// mainly to simplify handling of the packages in src/vendor +// and cmd/vendor.) +func lookupGorootExport(pkgDir string) (string, bool) { + f, ok := exportMap.Load(pkgDir) + if !ok { + var ( + listOnce sync.Once + exportPath string + ) + f, _ = exportMap.LoadOrStore(pkgDir, func() (string, bool) { + listOnce.Do(func() { + cmd := exec.Command("go", "list", "-export", "-f", "{{.Export}}", pkgDir) + cmd.Dir = build.Default.GOROOT + var output []byte + output, err := cmd.Output() + if err != nil { + return + } + + exports := strings.Split(string(bytes.TrimSpace(output)), "\n") + if len(exports) != 1 { + return + } + + exportPath = exports[0] + }) + + return exportPath, exportPath != "" + }) + } + + return f.(func() (string, bool))() +} diff --git a/internal/gcimporter/gcimporter.go b/internal/gcimporter/gcimporter.go index e2ad35139f3..6999498384c 100644 --- a/internal/gcimporter/gcimporter.go +++ b/internal/gcimporter/gcimporter.go @@ -23,17 +23,11 @@ package gcimporter // import "golang.org/x/tools/internal/gcimporter" import ( "bufio" - "bytes" "fmt" - "go/build" "go/token" "go/types" "io" "os" - "os/exec" - "path/filepath" - "strings" - "sync" ) const ( @@ -45,119 +39,6 @@ const ( trace = false ) -var exportMap sync.Map // package dir → func() (string, bool) - -// lookupGorootExport returns the location of the export data -// (normally found in the build cache, but located in GOROOT/pkg -// in prior Go releases) for the package located in pkgDir. -// -// (We use the package's directory instead of its import path -// mainly to simplify handling of the packages in src/vendor -// and cmd/vendor.) -func lookupGorootExport(pkgDir string) (string, bool) { - f, ok := exportMap.Load(pkgDir) - if !ok { - var ( - listOnce sync.Once - exportPath string - ) - f, _ = exportMap.LoadOrStore(pkgDir, func() (string, bool) { - listOnce.Do(func() { - cmd := exec.Command("go", "list", "-export", "-f", "{{.Export}}", pkgDir) - cmd.Dir = build.Default.GOROOT - var output []byte - output, err := cmd.Output() - if err != nil { - return - } - - exports := strings.Split(string(bytes.TrimSpace(output)), "\n") - if len(exports) != 1 { - return - } - - exportPath = exports[0] - }) - - return exportPath, exportPath != "" - }) - } - - return f.(func() (string, bool))() -} - -var pkgExts = [...]string{".a", ".o"} - -// FindPkg returns the filename and unique package id for an import -// path based on package information provided by build.Import (using -// the build.Default build.Context). A relative srcDir is interpreted -// relative to the current working directory. -// If no file was found, an empty filename is returned. -func FindPkg(path, srcDir string) (filename, id string) { - if path == "" { - return - } - - var noext string - switch { - default: - // "x" -> "$GOPATH/pkg/$GOOS_$GOARCH/x.ext", "x" - // Don't require the source files to be present. - if abs, err := filepath.Abs(srcDir); err == nil { // see issue 14282 - srcDir = abs - } - bp, _ := build.Import(path, srcDir, build.FindOnly|build.AllowBinary) - if bp.PkgObj == "" { - var ok bool - if bp.Goroot && bp.Dir != "" { - filename, ok = lookupGorootExport(bp.Dir) - } - if !ok { - id = path // make sure we have an id to print in error message - return - } - } else { - noext = strings.TrimSuffix(bp.PkgObj, ".a") - id = bp.ImportPath - } - - case build.IsLocalImport(path): - // "./x" -> "/this/directory/x.ext", "/this/directory/x" - noext = filepath.Join(srcDir, path) - id = noext - - case filepath.IsAbs(path): - // for completeness only - go/build.Import - // does not support absolute imports - // "/x" -> "/x.ext", "/x" - noext = path - id = path - } - - if false { // for debugging - if path != id { - fmt.Printf("%s -> %s\n", path, id) - } - } - - if filename != "" { - if f, err := os.Stat(filename); err == nil && !f.IsDir() { - return - } - } - - // try extensions - for _, ext := range pkgExts { - filename = noext + ext - if f, err := os.Stat(filename); err == nil && !f.IsDir() { - return - } - } - - filename = "" // not found - return -} - // Import imports a gc-generated package given its import path and srcDir, adds // the corresponding package object to the packages map, and returns the object. // The packages map must contain all packages already imported. From 82b6f75ce8a3f98c8b16806c8833045bdab8d33d Mon Sep 17 00:00:00 2001 From: Rob Findley Date: Tue, 10 Dec 2024 23:39:24 +0000 Subject: [PATCH 12/73] internal/typesinternal: rollback of Zero{Value,Expr} for gopls In CL 626537, the gopls behavior of formatting or generating a type expression for the zero value of a types.Type was changed to panic in the presence of invalid types. This leads to multiple panics in gopls (see tests). Fix by selectively rolling back this aspect of the change, as it relates to gopls. Fixes golang/go#70744 Change-Id: Ife9ae9a0fedf5a9d6b321cfb959f0d16eeec2986 Reviewed-on: https://go-review.googlesource.com/c/tools/+/634923 Reviewed-by: Alan Donovan LUCI-TryBot-Result: Go LUCI --- .../golang/completion/postfix_snippets.go | 3 ++- .../internal/golang/completion/statements.go | 5 ++-- gopls/internal/golang/completion/util.go | 25 +++++++++++++++++++ .../marker/testdata/completion/statements.txt | 21 ++++++++++++++++ internal/typesinternal/zerovalue.go | 7 ++++++ 5 files changed, 57 insertions(+), 4 deletions(-) diff --git a/gopls/internal/golang/completion/postfix_snippets.go b/gopls/internal/golang/completion/postfix_snippets.go index e0fc12cc9b5..e5b64954d86 100644 --- a/gopls/internal/golang/completion/postfix_snippets.go +++ b/gopls/internal/golang/completion/postfix_snippets.go @@ -442,7 +442,8 @@ func (a *postfixTmplArgs) TypeName(t types.Type) (string, error) { // Zero return the zero value representation of type t func (a *postfixTmplArgs) Zero(t types.Type) string { - return typesinternal.ZeroString(t, a.qf) + // TODO: use typesinternal.ZeroString, once it supports invalid types. + return formatZeroValue(t, a.qf) } func (a *postfixTmplArgs) IsIdent() bool { diff --git a/gopls/internal/golang/completion/statements.go b/gopls/internal/golang/completion/statements.go index e187bf2bee0..ce80cfb08ce 100644 --- a/gopls/internal/golang/completion/statements.go +++ b/gopls/internal/golang/completion/statements.go @@ -15,7 +15,6 @@ import ( "golang.org/x/tools/gopls/internal/golang" "golang.org/x/tools/gopls/internal/golang/completion/snippet" "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/internal/typesinternal" ) // addStatementCandidates adds full statement completion candidates @@ -295,7 +294,7 @@ func (c *completer) addErrCheck() { } else { snip.WriteText("return ") for i := 0; i < result.Len()-1; i++ { - snip.WriteText(typesinternal.ZeroString(result.At(i).Type(), c.qf)) + snip.WriteText(formatZeroValue(result.At(i).Type(), c.qf)) snip.WriteText(", ") } snip.WritePlaceholder(func(b *snippet.Builder) { @@ -405,7 +404,7 @@ func (c *completer) addReturnZeroValues() { fmt.Fprintf(&label, ", ") } - zero := typesinternal.ZeroString(result.At(i).Type(), c.qf) + zero := formatZeroValue(result.At(i).Type(), c.qf) snip.WritePlaceholder(func(b *snippet.Builder) { b.WriteText(zero) }) diff --git a/gopls/internal/golang/completion/util.go b/gopls/internal/golang/completion/util.go index 766484e2fc8..26d9050ec02 100644 --- a/gopls/internal/golang/completion/util.go +++ b/gopls/internal/golang/completion/util.go @@ -277,6 +277,31 @@ func prevStmt(pos token.Pos, path []ast.Node) ast.Stmt { return nil } +// formatZeroValue produces Go code representing the zero value of T. It +// returns the empty string if T is invalid. +// +// TODO(rfindley): use typesinternal.ZeroString once we can sort out how to +// propagate invalid types (see golang/go#70744). +func formatZeroValue(T types.Type, qf types.Qualifier) string { + switch u := T.Underlying().(type) { + case *types.Basic: + switch { + case u.Info()&types.IsNumeric > 0: + return "0" + case u.Info()&types.IsString > 0: + return `""` + case u.Info()&types.IsBoolean > 0: + return "false" + default: + return "" + } + case *types.Pointer, *types.Interface, *types.Chan, *types.Map, *types.Slice, *types.Signature: + return "nil" + default: + return types.TypeString(T, qf) + "{}" + } +} + // isBasicKind returns whether t is a basic type of kind k. func isBasicKind(t types.Type, k types.BasicInfo) bool { b, _ := t.Underlying().(*types.Basic) diff --git a/gopls/internal/test/marker/testdata/completion/statements.txt b/gopls/internal/test/marker/testdata/completion/statements.txt index 9856d938ea3..f189e8ec27f 100644 --- a/gopls/internal/test/marker/testdata/completion/statements.txt +++ b/gopls/internal/test/marker/testdata/completion/statements.txt @@ -98,6 +98,20 @@ func two() error { //@snippet("", stmtTwoIfErrReturn, "if s.err != nil {\n\treturn ${1:s.err}\n\\}") } +-- if_err_check_return3.go -- +package statements + +import "os" + +// Check that completion logic handles an invalid return type. +func badReturn() (NotAType, error) { + _, err := os.Open("foo") + //@snippet("", stmtOneIfErrReturn, "if err != nil {\n\treturn , ${1:err}\n\\}") + + _, err = os.Open("foo") + if er //@snippet(" //", stmtOneErrReturn, "err != nil {\n\treturn , ${1:err}\n\\}") +} + -- if_err_check_test.go -- package statements @@ -132,3 +146,10 @@ func foo() (int, string, error) { func bar() (int, string, error) { return //@snippet(" ", stmtReturnZeroValues, "return ${1:0}, ${2:\"\"}, ${3:nil}") } + + +//@item(stmtReturnInvalidValues, `return `) + +func invalidReturnStatement() NotAType { + return //@snippet(" ", stmtReturnInvalidValues, "return ${1:}") +} diff --git a/internal/typesinternal/zerovalue.go b/internal/typesinternal/zerovalue.go index 1066980649e..581876c4603 100644 --- a/internal/typesinternal/zerovalue.go +++ b/internal/typesinternal/zerovalue.go @@ -80,6 +80,11 @@ func ZeroString(t types.Type, qf types.Qualifier) string { // ZeroExpr is defined for types that are suitable for variables. // It may panic for other types such as Tuple or Union. // See [ZeroString] for a variant that returns a string. +// +// TODO(rfindley): improve the behavior of this function in the presence of +// invalid types. It should probably not return nil for +// types.Typ[types.Invalid], but must to preserve previous behavior. +// (golang/go#70744) func ZeroExpr(f *ast.File, pkg *types.Package, typ types.Type) ast.Expr { switch t := typ.(type) { case *types.Basic: @@ -94,6 +99,8 @@ func ZeroExpr(f *ast.File, pkg *types.Package, typ types.Type) ast.Expr { fallthrough case t.Kind() == types.UntypedNil: return ast.NewIdent("nil") + case t == types.Typ[types.Invalid]: + return nil default: panic(fmt.Sprint("ZeroExpr for unexpected type:", t)) } From 47df42f16952db6552a8748f25c20fd6e6972ce5 Mon Sep 17 00:00:00 2001 From: Tim King Date: Wed, 4 Dec 2024 10:46:36 -0800 Subject: [PATCH 13/73] internal/gcimporter: synchronizing FindPkg implementations Synchronizes the implementation of FindPkg with the implementation of FindPkg in $GOROOT/src/internal/exportdata/exportdata.go. This adds an error return value. Updates golang/go#70651 Change-Id: If0295b6d396ffca30ee75d958a50aa7f5b93848e Reviewed-on: https://go-review.googlesource.com/c/tools/+/633658 Commit-Queue: Tim King LUCI-TryBot-Result: Go LUCI Reviewed-by: Robert Findley --- internal/gcimporter/exportdata.go | 79 +++++++++++++++----------- internal/gcimporter/gcimporter.go | 4 +- internal/gcimporter/gcimporter_test.go | 8 +-- 3 files changed, 53 insertions(+), 38 deletions(-) diff --git a/internal/gcimporter/exportdata.go b/internal/gcimporter/exportdata.go index 6962b61af00..02a588c72f0 100644 --- a/internal/gcimporter/exportdata.go +++ b/internal/gcimporter/exportdata.go @@ -2,15 +2,15 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// This file is a copy of $GOROOT/src/go/internal/gcimporter/exportdata.go. - -// This file implements FindExportData. +// This file should be kept in sync with $GOROOT/src/internal/exportdata/exportdata.go. +// This file also additionally implements FindExportData for gcexportdata.NewReader. package gcimporter import ( "bufio" "bytes" + "errors" "fmt" "go/build" "os" @@ -110,10 +110,13 @@ func FindExportData(r *bufio.Reader) (size int64, err error) { // path based on package information provided by build.Import (using // the build.Default build.Context). A relative srcDir is interpreted // relative to the current working directory. -// If no file was found, an empty filename is returned. -func FindPkg(path, srcDir string) (filename, id string) { +// +// FindPkg is only used in tests within x/tools. +func FindPkg(path, srcDir string) (filename, id string, err error) { + // TODO(taking): Move internal/exportdata.FindPkg into its own file, + // and then this copy into a _test package. if path == "" { - return + return "", "", errors.New("path is empty") } var noext string @@ -124,20 +127,23 @@ func FindPkg(path, srcDir string) (filename, id string) { if abs, err := filepath.Abs(srcDir); err == nil { // see issue 14282 srcDir = abs } - bp, _ := build.Import(path, srcDir, build.FindOnly|build.AllowBinary) + var bp *build.Package + bp, err = build.Import(path, srcDir, build.FindOnly|build.AllowBinary) if bp.PkgObj == "" { - var ok bool if bp.Goroot && bp.Dir != "" { - filename, ok = lookupGorootExport(bp.Dir) - } - if !ok { - id = path // make sure we have an id to print in error message - return + filename, err = lookupGorootExport(bp.Dir) + if err == nil { + _, err = os.Stat(filename) + } + if err == nil { + return filename, bp.ImportPath, nil + } } + goto notfound } else { noext = strings.TrimSuffix(bp.PkgObj, ".a") - id = bp.ImportPath } + id = bp.ImportPath case build.IsLocalImport(path): // "./x" -> "/this/directory/x.ext", "/this/directory/x" @@ -158,27 +164,28 @@ func FindPkg(path, srcDir string) (filename, id string) { } } - if filename != "" { - if f, err := os.Stat(filename); err == nil && !f.IsDir() { - return - } - } - // try extensions for _, ext := range pkgExts { filename = noext + ext - if f, err := os.Stat(filename); err == nil && !f.IsDir() { - return + f, statErr := os.Stat(filename) + if statErr == nil && !f.IsDir() { + return filename, id, nil + } + if err == nil { + err = statErr } } - filename = "" // not found - return +notfound: + if err == nil { + return "", path, fmt.Errorf("can't find import: %q", path) + } + return "", path, fmt.Errorf("can't find import: %q: %w", path, err) } -var pkgExts = [...]string{".a", ".o"} +var pkgExts = [...]string{".a", ".o"} // a file from the build cache will have no extension -var exportMap sync.Map // package dir → func() (string, bool) +var exportMap sync.Map // package dir → func() (string, error) // lookupGorootExport returns the location of the export data // (normally found in the build cache, but located in GOROOT/pkg @@ -187,34 +194,42 @@ var exportMap sync.Map // package dir → func() (string, bool) // (We use the package's directory instead of its import path // mainly to simplify handling of the packages in src/vendor // and cmd/vendor.) -func lookupGorootExport(pkgDir string) (string, bool) { +// +// lookupGorootExport is only used in tests within x/tools. +func lookupGorootExport(pkgDir string) (string, error) { f, ok := exportMap.Load(pkgDir) if !ok { var ( listOnce sync.Once exportPath string + err error ) - f, _ = exportMap.LoadOrStore(pkgDir, func() (string, bool) { + f, _ = exportMap.LoadOrStore(pkgDir, func() (string, error) { listOnce.Do(func() { - cmd := exec.Command("go", "list", "-export", "-f", "{{.Export}}", pkgDir) + cmd := exec.Command(filepath.Join(build.Default.GOROOT, "bin", "go"), "list", "-export", "-f", "{{.Export}}", pkgDir) cmd.Dir = build.Default.GOROOT + cmd.Env = append(os.Environ(), "PWD="+cmd.Dir, "GOROOT="+build.Default.GOROOT) var output []byte - output, err := cmd.Output() + output, err = cmd.Output() if err != nil { + if ee, ok := err.(*exec.ExitError); ok && len(ee.Stderr) > 0 { + err = errors.New(string(ee.Stderr)) + } return } exports := strings.Split(string(bytes.TrimSpace(output)), "\n") if len(exports) != 1 { + err = fmt.Errorf("go list reported %d exports; expected 1", len(exports)) return } exportPath = exports[0] }) - return exportPath, exportPath != "" + return exportPath, err }) } - return f.(func() (string, bool))() + return f.(func() (string, error))() } diff --git a/internal/gcimporter/gcimporter.go b/internal/gcimporter/gcimporter.go index 6999498384c..77195babc11 100644 --- a/internal/gcimporter/gcimporter.go +++ b/internal/gcimporter/gcimporter.go @@ -65,12 +65,12 @@ func Import(fset *token.FileSet, packages map[string]*types.Package, path, srcDi } rc = f } else { - filename, id = FindPkg(path, srcDir) + filename, id, err = FindPkg(path, srcDir) if filename == "" { if path == "unsafe" { return types.Unsafe, nil } - return nil, fmt.Errorf("can't find import: %q", id) + return nil, err } // no need to re-import if the package was imported completely before diff --git a/internal/gcimporter/gcimporter_test.go b/internal/gcimporter/gcimporter_test.go index 20fbdef7f93..9b38a0e1e28 100644 --- a/internal/gcimporter/gcimporter_test.go +++ b/internal/gcimporter/gcimporter_test.go @@ -129,9 +129,9 @@ func testImportTestdata(t *testing.T) { packageFiles := map[string]string{} for _, pkg := range []string{"go/ast", "go/token"} { - export, _ := gcimporter.FindPkg(pkg, "testdata") + export, _, err := gcimporter.FindPkg(pkg, "testdata") if export == "" { - t.Fatalf("no export data found for %s", pkg) + t.Fatalf("no export data found for %s: %s", pkg, err) } packageFiles[pkg] = export } @@ -587,9 +587,9 @@ func TestIssue13566(t *testing.T) { t.Fatal(err) } - jsonExport, _ := gcimporter.FindPkg("encoding/json", "testdata") + jsonExport, _, err := gcimporter.FindPkg("encoding/json", "testdata") if jsonExport == "" { - t.Fatalf("no export data found for encoding/json") + t.Fatalf("no export data found for encoding/json: %s", err) } compilePkg(t, "testdata", "a.go", testoutdir, map[string]string{"encoding/json": jsonExport}, apkg(testoutdir)) From d405635ae7e0ad62bdf3d28c5d837ecc2545e16f Mon Sep 17 00:00:00 2001 From: Alan Donovan Date: Tue, 10 Dec 2024 15:24:23 -0500 Subject: [PATCH 14/73] internal/refactor/inline/analyzer: use "//go:fix inline" annotation This CL changes the proof-of-concept "inliner" analyzer to use "//go:fix inline" as the marker of a function that should be inlined. This seems to be the syntax emerging from the proposal golang/go#32816. Also, skip inlinings that cause literalization of the callee. Users of batch tools only want to see reductions. Updates golang/go#32816 Updates golang/go#70717 Change-Id: I6d70118f69b7c35f51e1d51863a0312b5dcc3b63 Reviewed-on: https://go-review.googlesource.com/c/tools/+/634919 Reviewed-by: Robert Findley LUCI-TryBot-Result: Go LUCI --- internal/refactor/inline/analyzer/analyzer.go | 80 +++++++++-------- .../refactor/inline/analyzer/directive.go | 90 +++++++++++++++++++ .../inline/analyzer/testdata/src/a/a.go | 8 +- .../analyzer/testdata/src/a/a.go.golden | 8 +- 4 files changed, 141 insertions(+), 45 deletions(-) create mode 100644 internal/refactor/inline/analyzer/directive.go diff --git a/internal/refactor/inline/analyzer/analyzer.go b/internal/refactor/inline/analyzer/analyzer.go index f0519469b5e..0e3fec82f95 100644 --- a/internal/refactor/inline/analyzer/analyzer.go +++ b/internal/refactor/inline/analyzer/analyzer.go @@ -9,8 +9,7 @@ import ( "go/ast" "go/token" "go/types" - "os" - "strings" + "slices" "golang.org/x/tools/go/analysis" "golang.org/x/tools/go/analysis/passes/inspect" @@ -20,28 +19,26 @@ import ( "golang.org/x/tools/internal/refactor/inline" ) -const Doc = `inline calls to functions with "inlineme" doc comment` +const Doc = `inline calls to functions with "//go:fix inline" doc comment` var Analyzer = &analysis.Analyzer{ Name: "inline", Doc: Doc, URL: "https://pkg.go.dev/golang.org/x/tools/internal/refactor/inline/analyzer", Run: run, - FactTypes: []analysis.Fact{new(inlineMeFact)}, + FactTypes: []analysis.Fact{new(goFixInlineFact)}, Requires: []*analysis.Analyzer{inspect.Analyzer}, } -func run(pass *analysis.Pass) (interface{}, error) { +func run(pass *analysis.Pass) (any, error) { // Memoize repeated calls for same file. - // TODO(adonovan): the analysis.Pass should abstract this (#62292) - // as the driver may not be reading directly from the file system. fileContent := make(map[string][]byte) readFile := func(node ast.Node) ([]byte, error) { filename := pass.Fset.File(node.Pos()).Name() content, ok := fileContent[filename] if !ok { var err error - content, err = os.ReadFile(filename) + content, err = pass.ReadFile(filename) if err != nil { return nil, err } @@ -50,40 +47,37 @@ func run(pass *analysis.Pass) (interface{}, error) { return content, nil } - // Pass 1: find functions annotated with an "inlineme" - // comment, and export a fact for each one. + // Pass 1: find functions annotated with a "//go:fix inline" + // comment (the syntax proposed by #32816), + // and export a fact for each one. inlinable := make(map[*types.Func]*inline.Callee) // memoization of fact import (nil => no fact) for _, file := range pass.Files { for _, decl := range file.Decls { - if decl, ok := decl.(*ast.FuncDecl); ok { - // TODO(adonovan): this is just a placeholder. - // Use the precise go:fix syntax in the proposal. - // Beware that //go: comments are treated specially - // by (*ast.CommentGroup).Text(). - // TODO(adonovan): alternatively, consider using - // the universal annotation mechanism sketched in - // https://go.dev/cl/489835 (which doesn't yet have - // a proper proposal). - if strings.Contains(decl.Doc.Text(), "inlineme") { - content, err := readFile(file) - if err != nil { - pass.Reportf(decl.Doc.Pos(), "invalid inlining candidate: cannot read source file: %v", err) - continue - } - callee, err := inline.AnalyzeCallee(discard, pass.Fset, pass.Pkg, pass.TypesInfo, decl, content) - if err != nil { - pass.Reportf(decl.Doc.Pos(), "invalid inlining candidate: %v", err) - continue - } - fn := pass.TypesInfo.Defs[decl.Name].(*types.Func) - pass.ExportObjectFact(fn, &inlineMeFact{callee}) - inlinable[fn] = callee + if decl, ok := decl.(*ast.FuncDecl); ok && + slices.ContainsFunc(directives(decl.Doc), func(d *directive) bool { + return d.Tool == "go" && d.Name == "fix" && d.Args == "inline" + }) { + + content, err := readFile(decl) + if err != nil { + pass.Reportf(decl.Doc.Pos(), "invalid inlining candidate: cannot read source file: %v", err) + continue + } + callee, err := inline.AnalyzeCallee(discard, pass.Fset, pass.Pkg, pass.TypesInfo, decl, content) + if err != nil { + pass.Reportf(decl.Doc.Pos(), "invalid inlining candidate: %v", err) + continue } + fn := pass.TypesInfo.Defs[decl.Name].(*types.Func) + pass.ExportObjectFact(fn, &goFixInlineFact{callee}) + inlinable[fn] = callee } } } // Pass 2. Inline each static call to an inlinable function. + // + // TODO(adonovan): handle multiple diffs that each add the same import. inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) nodeFilter := []ast.Node{ (*ast.File)(nil), @@ -100,7 +94,7 @@ func run(pass *analysis.Pass) (interface{}, error) { // Inlinable? callee, ok := inlinable[fn] if !ok { - var fact inlineMeFact + var fact goFixInlineFact if pass.ImportObjectFact(fn, &fact) { callee = fact.Callee inlinable[fn] = callee @@ -129,6 +123,16 @@ func run(pass *analysis.Pass) (interface{}, error) { pass.Reportf(call.Lparen, "%v", err) return } + if res.Literalized { + // Users are not fond of inlinings that literalize + // f(x) to func() { ... }(), so avoid them. + // + // (Unfortunately the inliner is very timid, + // and often literalizes when it cannot prove that + // reducing the call is safe; the user of this tool + // has no indication of what the problem is.) + return + } got := res.Content // Suggest the "fix". @@ -156,9 +160,11 @@ func run(pass *analysis.Pass) (interface{}, error) { return nil, nil } -type inlineMeFact struct{ Callee *inline.Callee } +// A goFixInlineFact is exported for each function marked "//go:fix inline". +// It holds information about the callee to support inlining. +type goFixInlineFact struct{ Callee *inline.Callee } -func (f *inlineMeFact) String() string { return "inlineme " + f.Callee.String() } -func (*inlineMeFact) AFact() {} +func (f *goFixInlineFact) String() string { return "goFixInline " + f.Callee.String() } +func (*goFixInlineFact) AFact() {} func discard(string, ...any) {} diff --git a/internal/refactor/inline/analyzer/directive.go b/internal/refactor/inline/analyzer/directive.go new file mode 100644 index 00000000000..f4426c5ffa8 --- /dev/null +++ b/internal/refactor/inline/analyzer/directive.go @@ -0,0 +1,90 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package analyzer + +import ( + "go/ast" + "go/token" + "strings" +) + +// -- plundered from the future (CL 605517, issue #68021) -- + +// TODO(adonovan): replace with ast.Directive after go1.24 (#68021). + +// A directive is a comment line with special meaning to the Go +// toolchain or another tool. It has the form: +// +// //tool:name args +// +// The "tool:" portion is missing for the three directives named +// line, extern, and export. +// +// See https://go.dev/doc/comment#Syntax for details of Go comment +// syntax and https://pkg.go.dev/cmd/compile#hdr-Compiler_Directives +// for details of directives used by the Go compiler. +type directive struct { + Pos token.Pos // of preceding "//" + Tool string + Name string + Args string // may contain internal spaces +} + +// directives returns the directives within the comment. +func directives(g *ast.CommentGroup) (res []*directive) { + if g != nil { + // Avoid (*ast.CommentGroup).Text() as it swallows directives. + for _, c := range g.List { + if len(c.Text) > 2 && + c.Text[1] == '/' && + c.Text[2] != ' ' && + isDirective(c.Text[2:]) { + + tool, nameargs, ok := strings.Cut(c.Text[2:], ":") + if !ok { + // Must be one of {line,extern,export}. + tool, nameargs = "", tool + } + name, args, _ := strings.Cut(nameargs, " ") // tab?? + res = append(res, &directive{ + Pos: c.Slash, + Tool: tool, + Name: name, + Args: strings.TrimSpace(args), + }) + } + } + } + return +} + +// isDirective reports whether c is a comment directive. +// This code is also in go/printer. +func isDirective(c string) bool { + // "//line " is a line directive. + // "//extern " is for gccgo. + // "//export " is for cgo. + // (The // has been removed.) + if strings.HasPrefix(c, "line ") || strings.HasPrefix(c, "extern ") || strings.HasPrefix(c, "export ") { + return true + } + + // "//[a-z0-9]+:[a-z0-9]" + // (The // has been removed.) + colon := strings.Index(c, ":") + if colon <= 0 || colon+1 >= len(c) { + return false + } + for i := 0; i <= colon+1; i++ { + if i == colon { + continue + } + b := c[i] + if !('a' <= b && b <= 'z' || '0' <= b && b <= '9') { + return false + } + } + return true +} diff --git a/internal/refactor/inline/analyzer/testdata/src/a/a.go b/internal/refactor/inline/analyzer/testdata/src/a/a.go index 294278670f2..6e159a36894 100644 --- a/internal/refactor/inline/analyzer/testdata/src/a/a.go +++ b/internal/refactor/inline/analyzer/testdata/src/a/a.go @@ -8,10 +8,10 @@ func f() { type T struct{} -// inlineme -func One() int { return one } // want One:`inlineme a.One` +//go:fix inline +func One() int { return one } // want One:`goFixInline a.One` const one = 1 -// inlineme -func (T) Two() int { return 2 } // want Two:`inlineme \(a.T\).Two` +//go:fix inline +func (T) Two() int { return 2 } // want Two:`goFixInline \(a.T\).Two` diff --git a/internal/refactor/inline/analyzer/testdata/src/a/a.go.golden b/internal/refactor/inline/analyzer/testdata/src/a/a.go.golden index 1a214fc9148..ea94f3b0175 100644 --- a/internal/refactor/inline/analyzer/testdata/src/a/a.go.golden +++ b/internal/refactor/inline/analyzer/testdata/src/a/a.go.golden @@ -8,10 +8,10 @@ func f() { type T struct{} -// inlineme -func One() int { return one } // want One:`inlineme a.One` +//go:fix inline +func One() int { return one } // want One:`goFixInline a.One` const one = 1 -// inlineme -func (T) Two() int { return 2 } // want Two:`inlineme \(a.T\).Two` +//go:fix inline +func (T) Two() int { return 2 } // want Two:`goFixInline \(a.T\).Two` From aca81ce84dbd90f1a2512eae1d87e5621edfbf39 Mon Sep 17 00:00:00 2001 From: Tim King Date: Wed, 4 Dec 2024 11:39:18 -0800 Subject: [PATCH 15/73] internal/gcimporter: copy GOROOT/internal/exportdata functions Copies functions from GOROOT/internal/exportdata as they are. Only local modification is to avoid saferio. Updates golang/go#70651 Change-Id: If2f4e503f69897c51b708f995dad2720b9f59e8d Reviewed-on: https://go-review.googlesource.com/c/tools/+/633659 Reviewed-by: Robert Findley Commit-Queue: Tim King LUCI-TryBot-Result: Go LUCI --- internal/gcimporter/exportdata.go | 211 ++++++++++++++++++++++++++++++ 1 file changed, 211 insertions(+) diff --git a/internal/gcimporter/exportdata.go b/internal/gcimporter/exportdata.go index 02a588c72f0..6fbb759c66c 100644 --- a/internal/gcimporter/exportdata.go +++ b/internal/gcimporter/exportdata.go @@ -13,6 +13,7 @@ import ( "errors" "fmt" "go/build" + "io" "os" "os/exec" "path/filepath" @@ -106,6 +107,216 @@ func FindExportData(r *bufio.Reader) (size int64, err error) { return } +// ReadUnified reads the contents of the unified export data from a reader r +// that contains the contents of a GC-created archive file. +// +// On success, the reader will be positioned after the end-of-section marker "\n$$\n". +// +// Supported GC-created archive files have 4 layers of nesting: +// - An archive file containing a package definition file. +// - The package definition file contains headers followed by a data section. +// Headers are lines (≤ 4kb) that do not start with "$$". +// - The data section starts with "$$B\n" followed by export data followed +// by an end of section marker "\n$$\n". (The section start "$$\n" is no +// longer supported.) +// - The export data starts with a format byte ('u') followed by the in +// the given format. (See ReadExportDataHeader for older formats.) +// +// Putting this together, the bytes in a GC-created archive files are expected +// to look like the following. +// See cmd/internal/archive for more details on ar file headers. +// +// | \n | ar file signature +// | __.PKGDEF...size...\n | ar header for __.PKGDEF including size. +// | go object <...>\n | objabi header +// | \n | other headers such as build id +// | $$B\n | binary format marker +// | u\n | unified export +// | $$\n | end-of-section marker +// | [optional padding] | padding byte (0x0A) if size is odd +// | [ar file header] | other ar files +// | [ar file data] | +func ReadUnified(r *bufio.Reader) (data []byte, err error) { + // We historically guaranteed headers at the default buffer size (4096) work. + // This ensures we can use ReadSlice throughout. + const minBufferSize = 4096 + r = bufio.NewReaderSize(r, minBufferSize) + + size, err := FindPackageDefinition(r) + if err != nil { + return + } + n := size + + objapi, headers, err := ReadObjectHeaders(r) + if err != nil { + return + } + n -= len(objapi) + for _, h := range headers { + n -= len(h) + } + + hdrlen, err := ReadExportDataHeader(r) + if err != nil { + return + } + n -= hdrlen + + // size also includes the end of section marker. Remove that many bytes from the end. + const marker = "\n$$\n" + n -= len(marker) + + if n < 0 { + err = fmt.Errorf("invalid size (%d) in the archive file: %d bytes remain without section headers (recompile package)", size, n) + return + } + + // Read n bytes from buf. + data = make([]byte, n) + _, err = io.ReadFull(r, data) + if err != nil { + return + } + + // Check for marker at the end. + var suffix [len(marker)]byte + _, err = io.ReadFull(r, suffix[:]) + if err != nil { + return + } + if s := string(suffix[:]); s != marker { + err = fmt.Errorf("read %q instead of end-of-section marker (%q)", s, marker) + return + } + + return +} + +// FindPackageDefinition positions the reader r at the beginning of a package +// definition file ("__.PKGDEF") within a GC-created archive by reading +// from it, and returns the size of the package definition file in the archive. +// +// The reader must be positioned at the start of the archive file before calling +// this function, and "__.PKGDEF" is assumed to be the first file in the archive. +// +// See cmd/internal/archive for details on the archive format. +func FindPackageDefinition(r *bufio.Reader) (size int, err error) { + // Uses ReadSlice to limit risk of malformed inputs. + + // Read first line to make sure this is an object file. + line, err := r.ReadSlice('\n') + if err != nil { + err = fmt.Errorf("can't find export data (%v)", err) + return + } + + // Is the first line an archive file signature? + if string(line) != "!\n" { + err = fmt.Errorf("not the start of an archive file (%q)", line) + return + } + + // package export block should be first + size = readArchiveHeader(r, "__.PKGDEF") + if size <= 0 { + err = fmt.Errorf("not a package file") + return + } + + return +} + +// ReadObjectHeaders reads object headers from the reader. Object headers are +// lines that do not start with an end-of-section marker "$$". The first header +// is the objabi header. On success, the reader will be positioned at the beginning +// of the end-of-section marker. +// +// It returns an error if any header does not fit in r.Size() bytes. +func ReadObjectHeaders(r *bufio.Reader) (objapi string, headers []string, err error) { + // line is a temporary buffer for headers. + // Use bounded reads (ReadSlice, Peek) to limit risk of malformed inputs. + var line []byte + + // objapi header should be the first line + if line, err = r.ReadSlice('\n'); err != nil { + err = fmt.Errorf("can't find export data (%v)", err) + return + } + objapi = string(line) + + // objapi header begins with "go object ". + if !strings.HasPrefix(objapi, "go object ") { + err = fmt.Errorf("not a go object file: %s", objapi) + return + } + + // process remaining object header lines + for { + // check for an end of section marker "$$" + line, err = r.Peek(2) + if err != nil { + return + } + if string(line) == "$$" { + return // stop + } + + // read next header + line, err = r.ReadSlice('\n') + if err != nil { + return + } + headers = append(headers, string(line)) + } +} + +// ReadExportDataHeader reads the export data header and format from r. +// It returns the number of bytes read, or an error if the format is no longer +// supported or it failed to read. +// +// The only currently supported format is binary export data in the +// unified export format. +func ReadExportDataHeader(r *bufio.Reader) (n int, err error) { + // Read export data header. + line, err := r.ReadSlice('\n') + if err != nil { + return + } + + hdr := string(line) + switch hdr { + case "$$\n": + err = fmt.Errorf("old textual export format no longer supported (recompile package)") + return + + case "$$B\n": + var format byte + format, err = r.ReadByte() + if err != nil { + return + } + // The unified export format starts with a 'u'. + switch format { + case 'u': + default: + // Older no longer supported export formats include: + // indexed export format which started with an 'i'; and + // the older binary export format which started with a 'c', + // 'd', or 'v' (from "version"). + err = fmt.Errorf("binary export format %q is no longer supported (recompile package)", format) + return + } + + default: + err = fmt.Errorf("unknown export data header: %q", hdr) + return + } + + n = len(hdr) + 1 // + 1 is for 'u' + return +} + // FindPkg returns the filename and unique package id for an import // path based on package information provided by build.Import (using // the build.Default build.Context). A relative srcDir is interpreted From dd4bf11f7bde3a9a2d11b61a61eefa85c93b3246 Mon Sep 17 00:00:00 2001 From: Alan Donovan Date: Tue, 10 Dec 2024 17:38:47 -0500 Subject: [PATCH 16/73] internal/gcimporter: set correct Package.Path for main packages The compiler emits Path="main" for main packages because that's the linker symbol prefix that it uses; but go/packages and x/tools in general wants types.Package.Path to represent the path as reported by go list. This CL works around the bug by decoding path="main" as the correct path for the package being decoded. + a test in go/packages Fixes golang/go#70742 Change-Id: I90f9234976cbfbe2a39c4da4852bc4a134fffd88 Reviewed-on: https://go-review.googlesource.com/c/tools/+/634922 Reviewed-by: Tim King Commit-Queue: Alan Donovan LUCI-TryBot-Result: Go LUCI Auto-Submit: Alan Donovan --- go/packages/packages_test.go | 25 +++++++++++++++++++++++++ internal/gcimporter/ureader_yes.go | 7 ++++++- 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/go/packages/packages_test.go b/go/packages/packages_test.go index 11c4f77dce4..b52be337ee2 100644 --- a/go/packages/packages_test.go +++ b/go/packages/packages_test.go @@ -3339,6 +3339,31 @@ func Foo() int { return a.Foo() } t.Logf("Packages: %+v", pkgs) } +// TestMainPackagePathInModeTypes tests (*types.Package).Path() for +// main packages in mode NeedTypes, a regression test for #70742, a +// bug in cmd/compile's export data that caused them to appear as +// "main". (The PkgPath field was always correct.) +func TestMainPackagePathInModeTypes(t *testing.T) { + testenv.NeedsGoPackages(t) + + cfg := &packages.Config{Mode: packages.NeedName | packages.NeedTypes} + pkgs, err := packages.Load(cfg, "cmd/go") + if err != nil { + t.Fatal(err) + } + p := pkgs[0] + if p.PkgPath != "cmd/go" || + p.Name != "main" || + p.Types.Path() != "cmd/go" || + p.Types.Name() != "main" { + t.Errorf("PkgPath=%q Name=%q Types.Path=%q Types.Name=%q; want (cmd/go, main) both times)", + p.PkgPath, + p.Name, + p.Types.Name(), + p.Types.Path()) + } +} + func writeTree(t *testing.T, archive string) string { root := t.TempDir() diff --git a/internal/gcimporter/ureader_yes.go b/internal/gcimporter/ureader_yes.go index 9b38187ec2b..6cdab448eca 100644 --- a/internal/gcimporter/ureader_yes.go +++ b/internal/gcimporter/ureader_yes.go @@ -264,7 +264,12 @@ func (pr *pkgReader) pkgIdx(idx pkgbits.Index) *types.Package { func (r *reader) doPkg() *types.Package { path := r.String() switch path { - case "": + // cmd/compile emits path="main" for main packages because + // that's the linker symbol prefix it used; but we need + // the package's path as it would be reported by go list, + // hence "main" below. + // See test at go/packages.TestMainPackagePathInModeTypes. + case "", "main": path = r.p.PkgPath() case "builtin": return nil // universe From c1ff179f13cf81078a747709a0a26698c425651e Mon Sep 17 00:00:00 2001 From: Alan Donovan Date: Mon, 9 Dec 2024 12:47:41 -0500 Subject: [PATCH 17/73] gopls/internal/golang: Implementations: support generics This CL adds support for generic types to the local (go/types-based) implementation of Implementations. Instead of using types.AssignableTo(C, I), which doesn't consider generics, we check that C has all the methods of I and that the signatures of each corresponding pair match when type parameters are allowed to stand for any type at all. A more precise implementation would use true unification, ensuring that only consistent substitutions are considered to match, but this is good enough for now. Perhaps when go/types adds a Unify API (#63982) this will be easier. See CL 634197 for the corresponding change to the global (fingerprint-based) implementation. Fixes golang/go#59224 Updates golang/go#63982 Change-Id: I6959f855b69436d52cafbe4de07bcf24b1e0f280 Reviewed-on: https://go-review.googlesource.com/c/tools/+/634596 Reviewed-by: Robert Findley Auto-Submit: Alan Donovan LUCI-TryBot-Result: Go LUCI --- .../internal/golang/completion/completion.go | 3 +- gopls/internal/golang/implementation.go | 184 ++++++++++++++++-- gopls/internal/golang/references.go | 5 +- .../implementation/generics-basicalias.txt | 26 +++ .../testdata/implementation/generics.txt | 20 +- 5 files changed, 208 insertions(+), 30 deletions(-) create mode 100644 gopls/internal/test/marker/testdata/implementation/generics-basicalias.txt diff --git a/gopls/internal/golang/completion/completion.go b/gopls/internal/golang/completion/completion.go index 3921eebf260..e534b5fd5dc 100644 --- a/gopls/internal/golang/completion/completion.go +++ b/gopls/internal/golang/completion/completion.go @@ -266,9 +266,10 @@ type completer struct { // matcher matches the candidates against the surrounding prefix. matcher matcher - // methodSetCache caches the types.NewMethodSet call, which is relatively + // methodSetCache caches the [types.NewMethodSet] call, which is relatively // expensive and can be called many times for the same type while searching // for deep completions. + // TODO(adonovan): use [typeutil.MethodSetCache], which exists for this purpose. methodSetCache map[methodSetKey]*types.MethodSet // tooNewSymbolsCache is a cache of diff --git a/gopls/internal/golang/implementation.go b/gopls/internal/golang/implementation.go index 2d504f4dda0..fe0a34a1c80 100644 --- a/gopls/internal/golang/implementation.go +++ b/gopls/internal/golang/implementation.go @@ -17,6 +17,7 @@ import ( "sync" "golang.org/x/sync/errgroup" + "golang.org/x/tools/go/types/typeutil" "golang.org/x/tools/gopls/internal/cache" "golang.org/x/tools/gopls/internal/cache/metadata" "golang.org/x/tools/gopls/internal/cache/methodsets" @@ -345,6 +346,8 @@ func implementsObj(ctx context.Context, snapshot *cache.Snapshot, uri protocol.D func localImplementations(ctx context.Context, snapshot *cache.Snapshot, pkg *cache.Package, queryType types.Type, method *types.Func) ([]protocol.Location, error) { queryType = methodsets.EnsurePointer(queryType) + var msets typeutil.MethodSetCache + // Scan through all type declarations in the syntax. var locs []protocol.Location var methodLocs []methodsets.Location @@ -366,16 +369,16 @@ func localImplementations(ctx context.Context, snapshot *cache.Snapshot, pkg *ca // The historical behavior enshrined by this // function rejects cases where both are // (nontrivial) interface types? - // That seems like useful information. + // That seems like useful information; see #68641. // TODO(adonovan): UX: report I/I pairs too? // The same question appears in the global algorithm (methodsets). - if !concreteImplementsIntf(candidateType, queryType) { + if !concreteImplementsIntf(&msets, candidateType, queryType) { return true // not assignable } // Ignore types with empty method sets. // (No point reporting that every type satisfies 'any'.) - mset := types.NewMethodSet(candidateType) + mset := msets.MethodSet(candidateType) if mset.Len() == 0 { return true } @@ -449,28 +452,173 @@ func errorLocation(ctx context.Context, snapshot *cache.Snapshot) (protocol.Loca return protocol.Location{}, fmt.Errorf("built-in error type not found") } -// concreteImplementsIntf returns true if a is an interface type implemented by -// concrete type b, or vice versa. -func concreteImplementsIntf(a, b types.Type) bool { - aIsIntf, bIsIntf := types.IsInterface(a), types.IsInterface(b) +// concreteImplementsIntf reports whether x is an interface type +// implemented by concrete type y, or vice versa. +// +// If one or both types are generic, the result indicates whether the +// interface may be implemented under some instantiation. +func concreteImplementsIntf(msets *typeutil.MethodSetCache, x, y types.Type) bool { + xiface := types.IsInterface(x) + yiface := types.IsInterface(y) // Make sure exactly one is an interface type. - if aIsIntf == bIsIntf { + // TODO(adonovan): rescind this policy choice and report + // I/I relationships. See CL 619719 + issue #68641. + if xiface == yiface { return false } - // Rearrange if needed so "a" is the concrete type. - if aIsIntf { - a, b = b, a + // Rearrange if needed so x is the concrete type. + if xiface { + x, y = y, x } + // Inv: y is an interface type. - // TODO(adonovan): this should really use GenericAssignableTo - // to report (e.g.) "ArrayList[T] implements List[T]", but - // GenericAssignableTo doesn't work correctly on pointers to - // generic named types. Thus the legacy implementation and the - // "local" part of implementations fail to report generics. - // The global algorithm based on subsets does the right thing. - return types.AssignableTo(a, b) + // For each interface method of y, check that x has it too. + // It is not necessary to compute x's complete method set. + // + // If y is a constraint interface (!y.IsMethodSet()), we + // ignore non-interface terms, leading to occasional spurious + // matches. We could in future filter based on them, but it + // would lead to divergence with the global (fingerprint-based) + // algorithm, which operates only on methodsets. + ymset := msets.MethodSet(y) + for i := range ymset.Len() { + ym := ymset.At(i).Obj().(*types.Func) + + xobj, _, _ := types.LookupFieldOrMethod(x, false, ym.Pkg(), ym.Name()) + xm, ok := xobj.(*types.Func) + if !ok { + return false // x lacks a method of y + } + if !unify(xm.Signature(), ym.Signature()) { + return false // signatures do not match + } + } + return true // all methods found +} + +// unify reports whether the types of x and y match, allowing free +// type parameters to stand for anything at all, without regard to +// consistency of substitutions. +// +// TODO(adonovan): implement proper unification (#63982), finding the +// most general unifier across all the interface methods. +// +// See also: unify in cache/methodsets/fingerprint, which uses a +// similar ersatz unification approach on type fingerprints, for +// the global index. +func unify(x, y types.Type) bool { + x = types.Unalias(x) + y = types.Unalias(y) + + // For now, allow a type parameter to match anything, + // without regard to consistency of substitutions. + if is[*types.TypeParam](x) || is[*types.TypeParam](y) { + return true + } + + if reflect.TypeOf(x) != reflect.TypeOf(y) { + return false // mismatched types + } + + switch x := x.(type) { + case *types.Array: + y := y.(*types.Array) + return x.Len() == y.Len() && + unify(x.Elem(), y.Elem()) + + case *types.Basic: + y := y.(*types.Basic) + return x.Kind() == y.Kind() + + case *types.Chan: + y := y.(*types.Chan) + return x.Dir() == y.Dir() && + unify(x.Elem(), y.Elem()) + + case *types.Interface: + y := y.(*types.Interface) + // TODO(adonovan): fix: for correctness, we must check + // that both interfaces have the same set of methods + // modulo type parameters, while avoiding the risk of + // unbounded interface recursion. + // + // Since non-empty interface literals are vanishingly + // rare in methods signatures, we ignore this for now. + // If more precision is needed we could compare method + // names and arities, still without full recursion. + return x.NumMethods() == y.NumMethods() + + case *types.Map: + y := y.(*types.Map) + return unify(x.Key(), y.Key()) && + unify(x.Elem(), y.Elem()) + + case *types.Named: + y := y.(*types.Named) + if x.Origin() != y.Origin() { + return false // different named types + } + xtargs := x.TypeArgs() + ytargs := y.TypeArgs() + if xtargs.Len() != ytargs.Len() { + return false // arity error (ill-typed) + } + for i := range xtargs.Len() { + if !unify(xtargs.At(i), ytargs.At(i)) { + return false // mismatched type args + } + } + return true + + case *types.Pointer: + y := y.(*types.Pointer) + return unify(x.Elem(), y.Elem()) + + case *types.Signature: + y := y.(*types.Signature) + return x.Variadic() == y.Variadic() && + unify(x.Params(), y.Params()) && + unify(x.Results(), y.Results()) + + case *types.Slice: + y := y.(*types.Slice) + return unify(x.Elem(), y.Elem()) + + case *types.Struct: + y := y.(*types.Struct) + if x.NumFields() != y.NumFields() { + return false + } + for i := range x.NumFields() { + xf := x.Field(i) + yf := y.Field(i) + if xf.Embedded() != yf.Embedded() || + xf.Name() != yf.Name() || + x.Tag(i) != y.Tag(i) || + !xf.Exported() && xf.Pkg() != yf.Pkg() || + !unify(xf.Type(), yf.Type()) { + return false + } + } + return true + + case *types.Tuple: + y := y.(*types.Tuple) + if x.Len() != y.Len() { + return false + } + for i := range x.Len() { + if !unify(x.At(i).Type(), y.At(i).Type()) { + return false + } + } + return true + + default: // incl. *Union, *TypeParam + panic(fmt.Sprintf("unexpected Type %#v", x)) + } } var ( diff --git a/gopls/internal/golang/references.go b/gopls/internal/golang/references.go index e42ddfa8a06..3ecaab6e3e1 100644 --- a/gopls/internal/golang/references.go +++ b/gopls/internal/golang/references.go @@ -25,6 +25,7 @@ import ( "golang.org/x/sync/errgroup" "golang.org/x/tools/go/types/objectpath" + "golang.org/x/tools/go/types/typeutil" "golang.org/x/tools/gopls/internal/cache" "golang.org/x/tools/gopls/internal/cache/metadata" "golang.org/x/tools/gopls/internal/cache/methodsets" @@ -579,6 +580,8 @@ func localReferences(pkg *cache.Package, targets map[types.Object]bool, correspo } } + var msets typeutil.MethodSetCache + // matches reports whether obj either is or corresponds to a target. // (Correspondence is defined as usual for interface methods.) matches := func(obj types.Object) bool { @@ -588,7 +591,7 @@ func localReferences(pkg *cache.Package, targets map[types.Object]bool, correspo if methodRecvs != nil && obj.Name() == methodName { if orecv := effectiveReceiver(obj); orecv != nil { for _, mrecv := range methodRecvs { - if concreteImplementsIntf(orecv, mrecv) { + if concreteImplementsIntf(&msets, orecv, mrecv) { return true } } diff --git a/gopls/internal/test/marker/testdata/implementation/generics-basicalias.txt b/gopls/internal/test/marker/testdata/implementation/generics-basicalias.txt new file mode 100644 index 00000000000..bd17a8a72ab --- /dev/null +++ b/gopls/internal/test/marker/testdata/implementation/generics-basicalias.txt @@ -0,0 +1,26 @@ +Test of special case of 'implementation' query: aliases of basic types +(rune vs int32) in the "tricky" (=generic) algorithm for unifying +method signatures. + +We test both the local (intra-package) and global (cross-package) +algorithms. + +-- go.mod -- +module example.com +go 1.18 + +-- a/a.go -- +package a + +type C[T any] struct{} +func (C[T]) F(rune, T) {} //@ loc(aCF, "F"), implementation("F", aIF, bIF) + +type I[T any] interface{ F(int32, T) } //@ loc(aIF, "F"), implementation("F", aCF, bCF) + +-- b/b.go -- +package b + +type C[T any] struct{} +func (C[T]) F(rune, T) {} //@ loc(bCF, "F"), implementation("F", aIF, bIF) + +type I[T any] interface{ F(int32, T) } //@ loc(bIF, "F"), implementation("F", aCF, bCF) diff --git a/gopls/internal/test/marker/testdata/implementation/generics.txt b/gopls/internal/test/marker/testdata/implementation/generics.txt index ba5ef3ff2e3..a526102890a 100644 --- a/gopls/internal/test/marker/testdata/implementation/generics.txt +++ b/gopls/internal/test/marker/testdata/implementation/generics.txt @@ -7,25 +7,25 @@ go 1.18 -- implementation/implementation.go -- package implementation -type GenIface[T any] interface { //@loc(GenIface, "GenIface"),implementation("GenIface", GC) - F(int, string, T) //@loc(GenIfaceF, "F"),implementation("F", GCF) +type GenIface[T any] interface { //@loc(GenIface, "GenIface"),implementation("GenIface", GC, GenConc, GenConcString) + F(int, string, T) //@loc(GenIfaceF, "F"),implementation("F", GCF, GenConcF) } -type GenConc[U any] int //@loc(GenConc, "GenConc"),implementation("GenConc", GI, GIString) +type GenConc[U any] int //@loc(GenConc, "GenConc"),implementation("GenConc", GI, GIString, GenIface) -func (GenConc[V]) F(int, string, V) {} //@loc(GenConcF, "F"),implementation("F", GIF) +func (GenConc[V]) F(int, string, V) {} //@loc(GenConcF, "F"),implementation("F", GIF, GenIfaceF) -type GenConcString struct{ GenConc[string] } //@loc(GenConcString, "GenConcString"),implementation(GenConcString, GIString, GI) +type GenConcString struct{ GenConc[string] } //@loc(GenConcString, "GenConcString"),implementation(GenConcString, GIString, GI, GenIface) -- other/other.go -- package other -type GI[T any] interface { //@loc(GI, "GI"),implementation("GI", GenConc, GenConcString) - F(int, string, T) //@loc(GIF, "F"),implementation("F", GenConcF) +type GI[T any] interface { //@loc(GI, "GI"),implementation("GI", GenConc, GenConcString, GC) + F(int, string, T) //@loc(GIF, "F"),implementation("F", GenConcF, GCF) } -type GIString GI[string] //@loc(GIString, "GIString"),implementation("GIString", GenConcString, GenConc) +type GIString GI[string] //@loc(GIString, "GIString"),implementation("GIString", GenConcString, GenConc, GC) -type GC[U any] int //@loc(GC, "GC"),implementation("GC", GenIface) +type GC[U any] int //@loc(GC, "GC"),implementation("GC", GenIface, GI, GIString) -func (GC[V]) F(int, string, V) {} //@loc(GCF, "F"),implementation("F", GenIfaceF) +func (GC[V]) F(int, string, V) {} //@loc(GCF, "F"),implementation("F", GenIfaceF, GIF) From c803483aac6e3244607faaadeb91a2be1dd6fee0 Mon Sep 17 00:00:00 2001 From: Tim King Date: Wed, 4 Dec 2024 12:27:22 -0800 Subject: [PATCH 18/73] internal/gcimporter: synchronize with internal/exportdata Use the copies of the functions from internal/exportdata. The implementations are now quite uniform between exportdata.go in GOROOT/src/internal/exportdata and internal/gcimporter as well as gcimporter.go in GOROOT/src/go/internal/gcimporter and internal/gcimporter. Fixes golang/go#70651 Change-Id: I8c2a55ca8c09d504315b9ccd7c676a5c2023ca68 Reviewed-on: https://go-review.googlesource.com/c/tools/+/633660 Commit-Queue: Tim King LUCI-TryBot-Result: Go LUCI Reviewed-by: Robert Griesemer Reviewed-by: Robert Findley --- internal/gcimporter/exportdata.go | 49 ++++++++----------------------- internal/gcimporter/gcimporter.go | 41 +++++--------------------- 2 files changed, 19 insertions(+), 71 deletions(-) diff --git a/internal/gcimporter/exportdata.go b/internal/gcimporter/exportdata.go index 6fbb759c66c..5662a311dac 100644 --- a/internal/gcimporter/exportdata.go +++ b/internal/gcimporter/exportdata.go @@ -31,58 +31,33 @@ import ( // accept inputs produced by the last two releases of cmd/compile, // plus tip. func FindExportData(r *bufio.Reader) (size int64, err error) { - // Read first line to make sure this is an object file. - line, err := r.ReadSlice('\n') + arsize, err := FindPackageDefinition(r) if err != nil { - err = fmt.Errorf("can't find export data (%v)", err) - return - } - - // Is the first line an archive file signature? - if string(line) != "!\n" { - err = fmt.Errorf("not the start of an archive file (%q)", line) - return - } - - // Archive file with the first file being __.PKGDEF. - arsize := readArchiveHeader(r, "__.PKGDEF") - if arsize <= 0 { - err = fmt.Errorf("not a package file") return } size = int64(arsize) - // Read first line of __.PKGDEF data, so that line - // is once again the first line of the input. - if line, err = r.ReadSlice('\n'); err != nil { - err = fmt.Errorf("can't find export data (%v)", err) - return - } - size -= int64(len(line)) - - // Now at __.PKGDEF in archive or still at beginning of file. - // Either way, line should begin with "go object ". - if !strings.HasPrefix(string(line), "go object ") { - err = fmt.Errorf("not a Go object file") + objapi, headers, err := ReadObjectHeaders(r) + if err != nil { return } - - // Skip over object headers to get to the export data section header "$$B\n". - // Object headers are lines that do not start with '$'. - for line[0] != '$' { - if line, err = r.ReadSlice('\n'); err != nil { - err = fmt.Errorf("can't find export data (%v)", err) - return - } - size -= int64(len(line)) + size -= int64(len(objapi)) + for _, h := range headers { + size -= int64(len(h)) } // Check for the binary export data section header "$$B\n". + // TODO(taking): Unify with ReadExportDataHeader so that it stops at the 'u' instead of reading + line, err := r.ReadSlice('\n') + if err != nil { + return + } hdr := string(line) if hdr != "$$B\n" { err = fmt.Errorf("unknown export data header: %q", hdr) return } + size -= int64(len(hdr)) // For files with a binary export data header "$$B\n", // these are always terminated by an end-of-section marker "\n$$\n". diff --git a/internal/gcimporter/gcimporter.go b/internal/gcimporter/gcimporter.go index 77195babc11..3dbd21d1b90 100644 --- a/internal/gcimporter/gcimporter.go +++ b/internal/gcimporter/gcimporter.go @@ -46,7 +46,7 @@ const ( // Import is only used in tests. func Import(fset *token.FileSet, packages map[string]*types.Package, path, srcDir string, lookup func(path string) (io.ReadCloser, error)) (pkg *types.Package, err error) { var rc io.ReadCloser - var filename, id string + var id string if lookup != nil { // With custom lookup specified, assume that caller has // converted path to a canonical import path for use in the map. @@ -65,6 +65,7 @@ func Import(fset *token.FileSet, packages map[string]*types.Package, path, srcDi } rc = f } else { + var filename string filename, id, err = FindPkg(path, srcDir) if filename == "" { if path == "unsafe" { @@ -93,43 +94,15 @@ func Import(fset *token.FileSet, packages map[string]*types.Package, path, srcDi } defer rc.Close() - var size int64 buf := bufio.NewReader(rc) - if size, err = FindExportData(buf); err != nil { - return - } - - var data []byte - data, err = io.ReadAll(buf) + data, err := ReadUnified(buf) if err != nil { + err = fmt.Errorf("import %q: %v", path, err) return } - if len(data) == 0 { - return nil, fmt.Errorf("no data to load a package from for path %s", id) - } - // Select appropriate importer. - switch data[0] { - case 'v', 'c', 'd': - // binary: emitted by cmd/compile till go1.10; obsolete. - return nil, fmt.Errorf("binary (%c) import format is no longer supported", data[0]) + // unified: emitted by cmd/compile since go1.20. + _, pkg, err = UImportData(fset, packages, data, id) - case 'i': - // indexed: emitted by cmd/compile till go1.19; - // now used only for serializing go/types. - // See https://github.com/golang/go/issues/69491. - return nil, fmt.Errorf("indexed (i) import format is no longer supported") - - case 'u': - // unified: emitted by cmd/compile since go1.20. - _, pkg, err := UImportData(fset, packages, data[1:size], id) - return pkg, err - - default: - l := len(data) - if l > 10 { - l = 10 - } - return nil, fmt.Errorf("unexpected export data with prefix %q for path %s", string(data[:l]), id) - } + return } From 7bfb27f1bc25b13ddf255c4e9699f60447d3936b Mon Sep 17 00:00:00 2001 From: Gopher Robot Date: Thu, 12 Dec 2024 13:26:04 -0800 Subject: [PATCH 19/73] gopls/go.mod: update dependencies following the v0.17.0 release This is an automated CL which updates the go.mod and go.sum. For golang/go#70301 Change-Id: Icadac268ef770a5551cddda3741ee169deb8c3f0 Reviewed-on: https://go-review.googlesource.com/c/tools/+/635796 LUCI-TryBot-Result: Go LUCI Reviewed-by: Robert Findley Auto-Submit: Gopher Robot Reviewed-by: Dmitri Shuralyov --- gopls/go.mod | 10 +++++----- gopls/go.sum | 16 ++++++++-------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/gopls/go.mod b/gopls/go.mod index 03f7956025d..ac2627490f3 100644 --- a/gopls/go.mod +++ b/gopls/go.mod @@ -6,14 +6,14 @@ go 1.23.1 require ( github.com/google/go-cmp v0.6.0 - github.com/jba/templatecheck v0.7.0 + github.com/jba/templatecheck v0.7.1 golang.org/x/mod v0.22.0 golang.org/x/sync v0.10.0 golang.org/x/sys v0.28.0 - golang.org/x/telemetry v0.0.0-20241106142447-58a1122356f5 + golang.org/x/telemetry v0.0.0-20241212155558-b40c91e7e8c7 golang.org/x/text v0.21.0 - golang.org/x/tools v0.21.1-0.20240531212143-b6235391adb3 - golang.org/x/vuln v1.0.4 + golang.org/x/tools v0.28.0 + golang.org/x/vuln v1.1.3 gopkg.in/yaml.v3 v3.0.1 honnef.co/go/tools v0.5.1 mvdan.cc/gofumpt v0.7.0 @@ -23,7 +23,7 @@ require ( require ( github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c // indirect github.com/google/safehtml v0.1.0 // indirect - golang.org/x/exp/typeparams v0.0.0-20231108232855-2478ac86f678 // indirect + golang.org/x/exp/typeparams v0.0.0-20241210194714-1829a127f884 // indirect gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect ) diff --git a/gopls/go.sum b/gopls/go.sum index 7785bbed7f6..0680e244d83 100644 --- a/gopls/go.sum +++ b/gopls/go.sum @@ -6,8 +6,8 @@ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/safehtml v0.1.0 h1:EwLKo8qawTKfsi0orxcQAZzu07cICaBeFMegAU9eaT8= github.com/google/safehtml v0.1.0/go.mod h1:L4KWwDsUJdECRAEpZoBn3O64bQaywRscowZjJAzjHnU= -github.com/jba/templatecheck v0.7.0 h1:wjTb/VhGgSFeim5zjWVePBdaMo28X74bGLSABZV+zIA= -github.com/jba/templatecheck v0.7.0/go.mod h1:n1Etw+Rrw1mDDD8dDRsEKTwMZsJ98EkktgNJC6wLUGo= +github.com/jba/templatecheck v0.7.1 h1:yOEIFazBEwzdTPYHZF3Pm81NF1ksxx1+vJncSEwvjKc= +github.com/jba/templatecheck v0.7.1/go.mod h1:n1Etw+Rrw1mDDD8dDRsEKTwMZsJ98EkktgNJC6wLUGo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -17,8 +17,8 @@ github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99 github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.30.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= -golang.org/x/exp/typeparams v0.0.0-20231108232855-2478ac86f678 h1:1P7xPZEwZMoBoz0Yze5Nx2/4pxj6nw9ZqHWXqP0iRgQ= -golang.org/x/exp/typeparams v0.0.0-20231108232855-2478ac86f678/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= +golang.org/x/exp/typeparams v0.0.0-20241210194714-1829a127f884 h1:1xaZTydL5Gsg78QharTwKfA9FY9CZ1VQj6D/AZEvHR0= +golang.org/x/exp/typeparams v0.0.0-20241210194714-1829a127f884/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= @@ -36,8 +36,8 @@ golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457/go.mod h1:pRgIJT+bRLFKnoM1ldnzKoxTIn14Yxz928LQRYYgIN0= -golang.org/x/telemetry v0.0.0-20241106142447-58a1122356f5 h1:TCDqnvbBsFapViksHcHySl/sW4+rTGNIAoJJesHRuMM= -golang.org/x/telemetry v0.0.0-20241106142447-58a1122356f5/go.mod h1:8nZWdGp9pq73ZI//QJyckMQab3yq7hoWi7SI0UIusVI= +golang.org/x/telemetry v0.0.0-20241212155558-b40c91e7e8c7 h1:Sb6e87CYvJ6ppWG5Bxk2RTqBuNUR2stvLegA9gcEEj4= +golang.org/x/telemetry v0.0.0-20241212155558-b40c91e7e8c7/go.mod h1:8h4Hgq+jcTvCDv2+i7NrfWwpYHcESleo2nGHxLbFLJ4= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= @@ -46,8 +46,8 @@ golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= -golang.org/x/vuln v1.0.4 h1:SP0mPeg2PmGCu03V+61EcQiOjmpri2XijexKdzv8Z1I= -golang.org/x/vuln v1.0.4/go.mod h1:NbJdUQhX8jY++FtuhrXs2Eyx0yePo9pF7nPlIjo9aaQ= +golang.org/x/vuln v1.1.3 h1:NPGnvPOTgnjBc9HTaUx+nj+EaUYxl5SJOWqaDYGaFYw= +golang.org/x/vuln v1.1.3/go.mod h1:7Le6Fadm5FOqE9C926BCD0g12NWyhg7cxV4BwcPFuNY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= From 81277617ebc6b14b0288ab9e26d49ebe0d3c0b90 Mon Sep 17 00:00:00 2001 From: Alan Donovan Date: Wed, 11 Dec 2024 17:43:46 -0500 Subject: [PATCH 20/73] gopls/internal/settings: drop experimental hoverKind=Structured We thought it was used by govim, but apparently not. Fixes golang/go#70233 Change-Id: Iacd85833dba91e870accf038e1c69a01e42fca1f Reviewed-on: https://go-review.googlesource.com/c/tools/+/635226 LUCI-TryBot-Result: Go LUCI Reviewed-by: Robert Findley --- gopls/doc/release/v0.18.0.md | 2 + gopls/doc/settings.md | 6 +- gopls/internal/doc/api.json | 6 +- gopls/internal/golang/hover.go | 181 ++++++++++------------- gopls/internal/settings/settings.go | 15 +- gopls/internal/settings/settings_test.go | 24 ++- 6 files changed, 111 insertions(+), 123 deletions(-) diff --git a/gopls/doc/release/v0.18.0.md b/gopls/doc/release/v0.18.0.md index f80eeea5929..2f2bcd9fc55 100644 --- a/gopls/doc/release/v0.18.0.md +++ b/gopls/doc/release/v0.18.0.md @@ -1,5 +1,7 @@ # Configuration Changes +- The experimental `hoverKind=Structured` setting is no longer supported. + # New features ## "Implementations" supports generics diff --git a/gopls/doc/settings.md b/gopls/doc/settings.md index 135fcca70af..81134fe0950 100644 --- a/gopls/doc/settings.md +++ b/gopls/doc/settings.md @@ -399,17 +399,13 @@ Default: `true`. ### `hoverKind enum` hoverKind controls the information that appears in the hover text. -SingleLine and Structured are intended for use only by authors of editor plugins. +SingleLine is intended for use only by authors of editor plugins. Must be one of: * `"FullDocumentation"` * `"NoDocumentation"` * `"SingleLine"` -* `"Structured"` is an experimental setting that returns a structured hover format. -This format separates the signature from the documentation, so that the client -can do more manipulation of these fields.\ -This should only be used by clients that support this behavior. * `"SynopsisDocumentation"` Default: `"FullDocumentation"`. diff --git a/gopls/internal/doc/api.json b/gopls/internal/doc/api.json index b64965ab863..1b195abe208 100644 --- a/gopls/internal/doc/api.json +++ b/gopls/internal/doc/api.json @@ -95,7 +95,7 @@ { "Name": "hoverKind", "Type": "enum", - "Doc": "hoverKind controls the information that appears in the hover text.\nSingleLine and Structured are intended for use only by authors of editor plugins.\n", + "Doc": "hoverKind controls the information that appears in the hover text.\nSingleLine is intended for use only by authors of editor plugins.\n", "EnumKeys": { "ValueType": "", "Keys": null @@ -113,10 +113,6 @@ "Value": "\"SingleLine\"", "Doc": "" }, - { - "Value": "\"Structured\"", - "Doc": "`\"Structured\"` is an experimental setting that returns a structured hover format.\nThis format separates the signature from the documentation, so that the client\ncan do more manipulation of these fields.\n\nThis should only be used by clients that support this behavior.\n" - }, { "Value": "\"SynopsisDocumentation\"", "Doc": "" diff --git a/gopls/internal/golang/hover.go b/gopls/internal/golang/hover.go index 3356a7db43a..5c55d3c2762 100644 --- a/gopls/internal/golang/hover.go +++ b/gopls/internal/golang/hover.go @@ -7,7 +7,6 @@ package golang import ( "bytes" "context" - "encoding/json" "fmt" "go/ast" "go/constant" @@ -45,50 +44,41 @@ import ( "golang.org/x/tools/internal/typesinternal" ) -// hoverJSON contains the structured result of a hover query. It is -// formatted in one of several formats as determined by the HoverKind -// setting, one of which is JSON. -// -// We believe this is used only by govim. -// TODO(adonovan): see if we can wean all clients of this interface. -type hoverJSON struct { - // Synopsis is a single sentence synopsis of the symbol's documentation. +// hoverResult contains the (internal) result of a hover query. +// It is formatted in one of several formats as determined by the +// HoverKind setting. +type hoverResult struct { + // synopsis is a single sentence synopsis of the symbol's documentation. // - // TODO(adonovan): in what syntax? It (usually) comes from doc.Synopsis, + // TODO(adonovan): in what syntax? It (usually) comes from doc.synopsis, // which produces "Text" form, but it may be fed to // DocCommentToMarkdown, which expects doc comment syntax. - Synopsis string `json:"synopsis"` + synopsis string - // FullDocumentation is the symbol's full documentation. - FullDocumentation string `json:"fullDocumentation"` + // fullDocumentation is the symbol's full documentation. + fullDocumentation string - // Signature is the symbol's signature. - Signature string `json:"signature"` + // signature is the symbol's signature. + signature string - // SingleLine is a single line describing the symbol. + // singleLine is a single line describing the symbol. // This is recommended only for use in clients that show a single line for hover. - SingleLine string `json:"singleLine"` + singleLine string - // SymbolName is the human-readable name to use for the symbol in links. - SymbolName string `json:"symbolName"` + // symbolName is the human-readable name to use for the symbol in links. + symbolName string - // LinkPath is the path of the package enclosing the given symbol, + // linkPath is the path of the package enclosing the given symbol, // with the module portion (if any) replaced by "module@version". // // For example: "github.com/google/go-github/v48@v48.1.0/github". // - // Use LinkTarget + "/" + LinkPath + "#" + LinkAnchor to form a pkgsite URL. - LinkPath string `json:"linkPath"` + // Use LinkTarget + "/" + linkPath + "#" + LinkAnchor to form a pkgsite URL. + linkPath string - // LinkAnchor is the pkg.go.dev link anchor for the given symbol. + // linkAnchor is the pkg.go.dev link anchor for the given symbol. // For example, the "Node" part of "pkg.go.dev/go/ast#Node". - LinkAnchor string `json:"linkAnchor"` - - // New fields go below, and are unexported. The existing - // exported fields are underspecified and have already - // constrained our movements too much. A detailed JSON - // interface might be nice, but it needs a design and a - // precise specification. + linkAnchor string // typeDecl is the declaration syntax for a type, // or "" for a non-type. @@ -142,7 +132,7 @@ func Hover(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, positi // if the position is valid but we fail to compute hover information. // // TODO(adonovan): strength-reduce file.Handle to protocol.DocumentURI. -func hover(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, pp protocol.Position) (protocol.Range, *hoverJSON, error) { +func hover(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, pp protocol.Position) (protocol.Range, *hoverResult, error) { // Check for hover inside the builtin file before attempting type checking // below. NarrowestPackageForFile may or may not succeed, depending on // whether this is a GOROOT view, but even if it does succeed the resulting @@ -241,14 +231,14 @@ func hover(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, pp pro // identifier. for _, spec := range pgf.File.Imports { if gastutil.NodeContains(spec, pos) { - rng, hoverJSON, err := hoverImport(ctx, snapshot, pkg, pgf, spec) + rng, hoverRes, err := hoverImport(ctx, snapshot, pkg, pgf, spec) if err != nil { return protocol.Range{}, nil, err } if hoverRange == nil { hoverRange = &rng } - return *hoverRange, hoverJSON, nil // (hoverJSON may be nil) + return *hoverRange, hoverRes, nil // (hoverRes may be nil) } } // Handle hovering over (non-import-path) literals. @@ -287,10 +277,10 @@ func hover(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, pp pro if selectedType != nil { fakeObj := types.NewVar(obj.Pos(), obj.Pkg(), obj.Name(), selectedType) signature := types.ObjectString(fakeObj, qf) - return *hoverRange, &hoverJSON{ - Signature: signature, - SingleLine: signature, - SymbolName: fakeObj.Name(), + return *hoverRange, &hoverResult{ + signature: signature, + singleLine: signature, + symbolName: fakeObj.Name(), }, nil } @@ -618,14 +608,14 @@ func hover(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, pp pro footer = fmt.Sprintf("Added in %v", sym.Version) } - return *hoverRange, &hoverJSON{ - Synopsis: doc.Synopsis(docText), - FullDocumentation: docText, - SingleLine: singleLineSignature, - SymbolName: linkName, - Signature: signature, - LinkPath: linkPath, - LinkAnchor: anchor, + return *hoverRange, &hoverResult{ + synopsis: doc.Synopsis(docText), + fullDocumentation: docText, + singleLine: singleLineSignature, + symbolName: linkName, + signature: signature, + linkPath: linkPath, + linkAnchor: anchor, typeDecl: typeDecl, methods: methods, promotedFields: fields, @@ -635,15 +625,15 @@ func hover(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, pp pro // hoverBuiltin computes hover information when hovering over a builtin // identifier. -func hoverBuiltin(ctx context.Context, snapshot *cache.Snapshot, obj types.Object) (*hoverJSON, error) { +func hoverBuiltin(ctx context.Context, snapshot *cache.Snapshot, obj types.Object) (*hoverResult, error) { // Special handling for error.Error, which is the only builtin method. // // TODO(rfindley): can this be unified with the handling below? if obj.Name() == "Error" { signature := obj.String() - return &hoverJSON{ - Signature: signature, - SingleLine: signature, + return &hoverResult{ + signature: signature, + singleLine: signature, // TODO(rfindley): these are better than the current behavior. // SymbolName: "(error).Error", // LinkPath: "builtin", @@ -685,14 +675,14 @@ func hoverBuiltin(ctx context.Context, snapshot *cache.Snapshot, obj types.Objec signature = replacer.Replace(signature) docText := comment.Text() - return &hoverJSON{ - Synopsis: doc.Synopsis(docText), - FullDocumentation: docText, - Signature: signature, - SingleLine: obj.String(), - SymbolName: obj.Name(), - LinkPath: "builtin", - LinkAnchor: obj.Name(), + return &hoverResult{ + synopsis: doc.Synopsis(docText), + fullDocumentation: docText, + signature: signature, + singleLine: obj.String(), + symbolName: obj.Name(), + linkPath: "builtin", + linkAnchor: obj.Name(), }, nil } @@ -700,7 +690,7 @@ func hoverBuiltin(ctx context.Context, snapshot *cache.Snapshot, obj types.Objec // imp in the file pgf of pkg. // // If we do not have metadata for the hovered import, it returns _ -func hoverImport(ctx context.Context, snapshot *cache.Snapshot, pkg *cache.Package, pgf *parsego.File, imp *ast.ImportSpec) (protocol.Range, *hoverJSON, error) { +func hoverImport(ctx context.Context, snapshot *cache.Snapshot, pkg *cache.Package, pgf *parsego.File, imp *ast.ImportSpec) (protocol.Range, *hoverResult, error) { rng, err := pgf.NodeRange(imp.Path) if err != nil { return protocol.Range{}, nil, err @@ -743,16 +733,16 @@ func hoverImport(ctx context.Context, snapshot *cache.Snapshot, pkg *cache.Packa } docText := comment.Text() - return rng, &hoverJSON{ - Signature: "package " + string(impMetadata.Name), - Synopsis: doc.Synopsis(docText), - FullDocumentation: docText, + return rng, &hoverResult{ + signature: "package " + string(impMetadata.Name), + synopsis: doc.Synopsis(docText), + fullDocumentation: docText, }, nil } // hoverPackageName computes hover information for the package name of the file // pgf in pkg. -func hoverPackageName(pkg *cache.Package, pgf *parsego.File) (protocol.Range, *hoverJSON, error) { +func hoverPackageName(pkg *cache.Package, pgf *parsego.File) (protocol.Range, *hoverResult, error) { var comment *ast.CommentGroup for _, pgf := range pkg.CompiledGoFiles() { if pgf.File.Doc != nil { @@ -801,10 +791,10 @@ func hoverPackageName(pkg *cache.Package, pgf *parsego.File) (protocol.Range, *h footer += fmt.Sprintf(" - %s: %s", attr.title, attr.value) } - return rng, &hoverJSON{ - Signature: "package " + string(pkg.Metadata().Name), - Synopsis: doc.Synopsis(docText), - FullDocumentation: docText, + return rng, &hoverResult{ + signature: "package " + string(pkg.Metadata().Name), + synopsis: doc.Synopsis(docText), + fullDocumentation: docText, footer: footer, }, nil } @@ -816,7 +806,7 @@ func hoverPackageName(pkg *cache.Package, pgf *parsego.File) (protocol.Range, *h // For example, hovering over "\u2211" in "foo \u2211 bar" yields: // // '∑', U+2211, N-ARY SUMMATION -func hoverLit(pgf *parsego.File, lit *ast.BasicLit, pos token.Pos) (protocol.Range, *hoverJSON, error) { +func hoverLit(pgf *parsego.File, lit *ast.BasicLit, pos token.Pos) (protocol.Range, *hoverResult, error) { var ( value string // if non-empty, a constant value to format in hover r rune // if non-zero, format a description of this rune in hover @@ -929,15 +919,15 @@ func hoverLit(pgf *parsego.File, lit *ast.BasicLit, pos token.Pos) (protocol.Ran fmt.Fprintf(&b, "U+%04X, %s", r, runeName) } hover := b.String() - return rng, &hoverJSON{ - Synopsis: hover, - FullDocumentation: hover, + return rng, &hoverResult{ + synopsis: hover, + fullDocumentation: hover, }, nil } // hoverEmbed computes hover information for a filepath.Match pattern. // Assumes that the pattern is relative to the location of fh. -func hoverEmbed(fh file.Handle, rng protocol.Range, pattern string) (protocol.Range, *hoverJSON, error) { +func hoverEmbed(fh file.Handle, rng protocol.Range, pattern string) (protocol.Range, *hoverResult, error) { s := &strings.Builder{} dir := fh.URI().DirPath() @@ -969,12 +959,12 @@ func hoverEmbed(fh file.Handle, rng protocol.Range, pattern string) (protocol.Ra fmt.Fprintf(s, "%s\n\n", m) } - json := &hoverJSON{ - Signature: fmt.Sprintf("Embedding %q", pattern), - Synopsis: s.String(), - FullDocumentation: s.String(), + res := &hoverResult{ + signature: fmt.Sprintf("Embedding %q", pattern), + synopsis: s.String(), + fullDocumentation: s.String(), } - return rng, json, nil + return rng, res, nil } // inferredSignatureString is a wrapper around the types.ObjectString function @@ -1196,7 +1186,7 @@ func parseFull(ctx context.Context, snapshot *cache.Snapshot, fset *token.FileSe } // If pkgURL is non-nil, it should be used to generate doc links. -func formatHover(h *hoverJSON, options *settings.Options, pkgURL func(path PackagePath, fragment string) protocol.URI) (string, error) { +func formatHover(h *hoverResult, options *settings.Options, pkgURL func(path PackagePath, fragment string) protocol.URI) (string, error) { markdown := options.PreferredContentFormat == protocol.Markdown maybeFenced := func(s string) string { if s != "" && markdown { @@ -1207,17 +1197,10 @@ func formatHover(h *hoverJSON, options *settings.Options, pkgURL func(path Packa switch options.HoverKind { case settings.SingleLine: - return h.SingleLine, nil + return h.singleLine, nil case settings.NoDocumentation: - return maybeFenced(h.Signature), nil - - case settings.Structured: - b, err := json.Marshal(h) - if err != nil { - return "", err - } - return string(b), nil + return maybeFenced(h.signature), nil case settings.SynopsisDocumentation, settings.FullDocumentation: var sections [][]string // assembled below @@ -1228,20 +1211,20 @@ func formatHover(h *hoverJSON, options *settings.Options, pkgURL func(path Packa // but not Signature, which is redundant (= TypeDecl + "\n" + Methods). // For all other symbols, we display Signature; // TypeDecl and Methods are empty. - // (This awkwardness is to preserve JSON compatibility.) + // (Now that JSON is no more, we could rationalize this.) if h.typeDecl != "" { sections = append(sections, []string{maybeFenced(h.typeDecl)}) } else { - sections = append(sections, []string{maybeFenced(h.Signature)}) + sections = append(sections, []string{maybeFenced(h.signature)}) } // Doc section. var doc string switch options.HoverKind { case settings.SynopsisDocumentation: - doc = h.Synopsis + doc = h.synopsis case settings.FullDocumentation: - doc = h.FullDocumentation + doc = h.fullDocumentation } if options.PreferredContentFormat == protocol.Markdown { doc = DocCommentToMarkdown(doc, options) @@ -1363,35 +1346,35 @@ func StdSymbolOf(obj types.Object) *stdlib.Symbol { } // If pkgURL is non-nil, it should be used to generate doc links. -func formatLink(h *hoverJSON, options *settings.Options, pkgURL func(path PackagePath, fragment string) protocol.URI) string { - if options.LinksInHover == settings.LinksInHover_None || h.LinkPath == "" { +func formatLink(h *hoverResult, options *settings.Options, pkgURL func(path PackagePath, fragment string) protocol.URI) string { + if options.LinksInHover == settings.LinksInHover_None || h.linkPath == "" { return "" } var url protocol.URI var caption string if pkgURL != nil { // LinksInHover == "gopls" // Discard optional module version portion. - // (Ideally the hoverJSON would retain the structure...) - path := h.LinkPath - if module, versionDir, ok := strings.Cut(h.LinkPath, "@"); ok { + // (Ideally the hoverResult would retain the structure...) + path := h.linkPath + if module, versionDir, ok := strings.Cut(h.linkPath, "@"); ok { // "module@version/dir" path = module if _, dir, ok := strings.Cut(versionDir, "/"); ok { path += "/" + dir } } - url = pkgURL(PackagePath(path), h.LinkAnchor) + url = pkgURL(PackagePath(path), h.linkAnchor) caption = "in gopls doc viewer" } else { if options.LinkTarget == "" { return "" } - url = cache.BuildLink(options.LinkTarget, h.LinkPath, h.LinkAnchor) + url = cache.BuildLink(options.LinkTarget, h.linkPath, h.linkAnchor) caption = "on " + options.LinkTarget } switch options.PreferredContentFormat { case protocol.Markdown: - return fmt.Sprintf("[`%s` %s](%s)", h.SymbolName, caption, url) + return fmt.Sprintf("[`%s` %s](%s)", h.symbolName, caption, url) case protocol.PlainText: return "" default: diff --git a/gopls/internal/settings/settings.go b/gopls/internal/settings/settings.go index 5f1efef040d..4dd85a59182 100644 --- a/gopls/internal/settings/settings.go +++ b/gopls/internal/settings/settings.go @@ -346,7 +346,7 @@ type CompletionOptions struct { // Note: DocumentationOptions must be comparable with reflect.DeepEqual. type DocumentationOptions struct { // HoverKind controls the information that appears in the hover text. - // SingleLine and Structured are intended for use only by authors of editor plugins. + // SingleLine is intended for use only by authors of editor plugins. HoverKind HoverKind // LinkTarget is the base URL for links to Go package @@ -791,13 +791,6 @@ const ( NoDocumentation HoverKind = "NoDocumentation" SynopsisDocumentation HoverKind = "SynopsisDocumentation" FullDocumentation HoverKind = "FullDocumentation" - - // Structured is an experimental setting that returns a structured hover format. - // This format separates the signature from the documentation, so that the client - // can do more manipulation of these fields. - // - // This should only be used by clients that support this behavior. - Structured HoverKind = "Structured" ) type VulncheckMode string @@ -1021,12 +1014,14 @@ func (o *Options) setOne(name string, value any) error { AllSymbolScope) case "hoverKind": + if s, ok := value.(string); ok && strings.EqualFold(s, "structured") { + return deprecatedError("the experimental hoverKind='structured' setting was removed in gopls/v0.18.0 (https://go.dev/issue/70233)") + } return setEnum(&o.HoverKind, value, NoDocumentation, SingleLine, SynopsisDocumentation, - FullDocumentation, - Structured) + FullDocumentation) case "linkTarget": return setString(&o.LinkTarget, value) diff --git a/gopls/internal/settings/settings_test.go b/gopls/internal/settings/settings_test.go index 6f865083a9d..345ec81574f 100644 --- a/gopls/internal/settings/settings_test.go +++ b/gopls/internal/settings/settings_test.go @@ -90,18 +90,34 @@ func TestOptions_Set(t *testing.T) { return o.HoverKind == SingleLine }, }, + { + name: "hoverKind", + value: "Structured", + wantError: true, + check: func(o Options) bool { + return o.HoverKind == FullDocumentation + }, + }, + { + name: "ui.documentation.hoverKind", + value: "Structured", + wantError: true, + check: func(o Options) bool { + return o.HoverKind == FullDocumentation + }, + }, { name: "hoverKind", - value: "Structured", + value: "FullDocumentation", check: func(o Options) bool { - return o.HoverKind == Structured + return o.HoverKind == FullDocumentation }, }, { name: "ui.documentation.hoverKind", - value: "Structured", + value: "FullDocumentation", check: func(o Options) bool { - return o.HoverKind == Structured + return o.HoverKind == FullDocumentation }, }, { From e3a4b6bb9bb66d904fbee4b8e6e2a0f23b08bbba Mon Sep 17 00:00:00 2001 From: Tim King Date: Thu, 12 Dec 2024 15:00:43 -0800 Subject: [PATCH 21/73] internal/gcimporter: update comment Remove a reference to GOROOT/src/go/internal/gcimporter/iimport.go This file no longer exists. Change-Id: Ia5e012b0c39a71390167a00f0890b7492d789188 Reviewed-on: https://go-review.googlesource.com/c/tools/+/635798 Reviewed-by: Robert Findley LUCI-TryBot-Result: Go LUCI Commit-Queue: Tim King --- internal/gcimporter/iimport.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/internal/gcimporter/iimport.go b/internal/gcimporter/iimport.go index 7f8794e8625..69b1d697cbe 100644 --- a/internal/gcimporter/iimport.go +++ b/internal/gcimporter/iimport.go @@ -5,8 +5,6 @@ // Indexed package import. // See iexport.go for the export data format. -// This file is a copy of $GOROOT/src/go/internal/gcimporter/iimport.go. - package gcimporter import ( From a255cbe787ca30729be03de7c191a52d71f75d20 Mon Sep 17 00:00:00 2001 From: Peter Weinberger Date: Tue, 10 Dec 2024 12:32:47 -0500 Subject: [PATCH 22/73] internal/modindex: handle deprecated symbols Mark deprecated symbols in the index and include a Deprecated field in the returned Candidates. (That it, it is up to the user of the index to decide what to do about deprecated symbols.) Change-Id: Ibc1e5c3b0569c288eed3e92b5043b3eac1e9d7ab Reviewed-on: https://go-review.googlesource.com/c/tools/+/634917 LUCI-TryBot-Result: Go LUCI Reviewed-by: Robert Findley --- internal/modindex/lookup.go | 5 +++- internal/modindex/lookup_test.go | 29 +++++++++++++---------- internal/modindex/symbols.go | 40 +++++++++++++++++++++++++++----- 3 files changed, 55 insertions(+), 19 deletions(-) diff --git a/internal/modindex/lookup.go b/internal/modindex/lookup.go index 29d4e3d7a39..012fdd7134c 100644 --- a/internal/modindex/lookup.go +++ b/internal/modindex/lookup.go @@ -16,6 +16,7 @@ type Candidate struct { Dir string ImportPath string Type LexType + Deprecated bool // information for Funcs Results int16 // how many results Sig []Field // arg names and types @@ -79,8 +80,9 @@ func (ix *Index) Lookup(pkg, name string, prefix bool) []Candidate { Dir: string(e.Dir), ImportPath: e.ImportPath, Type: asLexType(flds[1][0]), + Deprecated: len(flds[1]) > 1 && flds[1][1] == 'D', } - if flds[1] == "F" { + if px.Type == Func { n, err := strconv.Atoi(flds[2]) if err != nil { continue // should never happen @@ -111,6 +113,7 @@ func toFields(sig []string) []Field { } // benchmarks show this is measurably better than strings.Split +// split into first 4 fields separated by single space func fastSplit(x string) []string { ans := make([]string, 0, 4) nxt := 0 diff --git a/internal/modindex/lookup_test.go b/internal/modindex/lookup_test.go index 6a663554d73..4c5ae35695d 100644 --- a/internal/modindex/lookup_test.go +++ b/internal/modindex/lookup_test.go @@ -28,28 +28,34 @@ var thedata = tdata{ fname: "cloud.google.com/go/longrunning@v0.4.1/foo.go", pkg: "foo", items: []titem{ - // these need to be in alphabetical order by symbol - {"func Foo() {}", result{"Foo", Func, 0, nil}}, - {"const FooC = 23", result{"FooC", Const, 0, nil}}, - {"func FooF(int, float) error {return nil}", result{"FooF", Func, 1, + // these need to be in alphabetical order + {"func Foo() {}", result{"Foo", Func, false, 0, nil}}, + {"const FooC = 23", result{"FooC", Const, false, 0, nil}}, + {"func FooF(int, float) error {return nil}", result{"FooF", Func, false, 1, []Field{{"_", "int"}, {"_", "float"}}}}, - {"type FooT struct{}", result{"FooT", Type, 0, nil}}, - {"var FooV int", result{"FooV", Var, 0, nil}}, - {"func Ⱋoox(x int) {}", result{"Ⱋoox", Func, 0, []Field{{"x", "int"}}}}, + {"type FooT struct{}", result{"FooT", Type, false, 0, nil}}, + {"var FooV int", result{"FooV", Var, false, 0, nil}}, + {"func Goo() {}", result{"Goo", Func, false, 0, nil}}, + {"/*Deprecated: too weird\n*/\n// Another Goo\nvar GooVV int", result{"GooVV", Var, true, 0, nil}}, + {"func Ⱋoox(x int) {}", result{"Ⱋoox", Func, false, 0, []Field{{"x", "int"}}}}, }, } type result struct { - name string - typ LexType - result int - sig []Field + name string + typ LexType + deprecated bool + result int + sig []Field } func okresult(r result, p Candidate) bool { if r.name != p.Name || r.typ != p.Type || r.result != int(p.Results) { return false } + if r.deprecated != p.Deprecated { + return false + } if len(r.sig) != len(p.Sig) { return false } @@ -78,7 +84,6 @@ func TestLookup(t *testing.T) { // get all the symbols p := ix.Lookup("foo", "", true) if len(p) != len(thedata.items) { - // we should have gotten them all t.Errorf("got %d possibilities for pkg foo, expected %d", len(p), len(thedata.items)) } for i, r := range thedata.items { diff --git a/internal/modindex/symbols.go b/internal/modindex/symbols.go index 2e285ed996a..33bf2641f7b 100644 --- a/internal/modindex/symbols.go +++ b/internal/modindex/symbols.go @@ -19,12 +19,13 @@ import ( ) // The name of a symbol contains information about the symbol: -// T for types -// C for consts -// V for vars +// T for types, TD if the type is deprecated +// C for consts, CD if the const is deprecated +// V for vars, VD if the var is deprecated // and for funcs: F ( )* // any spaces in are replaced by $s so that the fields -// of the name are space separated +// of the name are space separated. F is replaced by FD if the func +// is deprecated. type symbol struct { pkg string // name of the symbols's package name string // declared name @@ -41,7 +42,7 @@ func getSymbols(cd Abspath, dirs map[string][]*directory) { d := vv[0] g.Go(func() error { thedir := filepath.Join(string(cd), string(d.path)) - mode := parser.SkipObjectResolution + mode := parser.SkipObjectResolution | parser.ParseComments fi, err := os.ReadDir(thedir) if err != nil { @@ -84,6 +85,9 @@ func getFileExports(f *ast.File) []symbol { // generic functions just like non-generic ones. sig := dtype.Params kind := "F" + if isDeprecated(decl.Doc) { + kind += "D" + } result := []string{fmt.Sprintf("%d", dtype.Results.NumFields())} for _, x := range sig.List { // This code creates a string representing the type. @@ -127,12 +131,16 @@ func getFileExports(f *ast.File) []symbol { ans = append(ans, *s) } case *ast.GenDecl: + depr := isDeprecated(decl.Doc) switch decl.Tok { case token.CONST, token.VAR: tp := "V" if decl.Tok == token.CONST { tp = "C" } + if depr { + tp += "D" + } for _, sp := range decl.Specs { for _, x := range sp.(*ast.ValueSpec).Names { if s := newsym(pkg, x.Name, tp, ""); s != nil { @@ -141,8 +149,12 @@ func getFileExports(f *ast.File) []symbol { } } case token.TYPE: + tp := "T" + if depr { + tp += "D" + } for _, sp := range decl.Specs { - if s := newsym(pkg, sp.(*ast.TypeSpec).Name.Name, "T", ""); s != nil { + if s := newsym(pkg, sp.(*ast.TypeSpec).Name.Name, tp, ""); s != nil { ans = append(ans, *s) } } @@ -160,6 +172,22 @@ func newsym(pkg, name, kind, sig string) *symbol { return &sym } +func isDeprecated(doc *ast.CommentGroup) bool { + if doc == nil { + return false + } + // go.dev/wiki/Deprecated Paragraph starting 'Deprecated:' + // This code fails for /* Deprecated: */, but it's the code from + // gopls/internal/analysis/deprecated + lines := strings.Split(doc.Text(), "\n\n") + for _, line := range lines { + if strings.HasPrefix(line, "Deprecated:") { + return true + } + } + return false +} + // return the package name and the value for the symbols. // if there are multiple packages, choose one arbitrarily // the returned slice is sorted lexicographically From c73f4c81122bfcd7c9a6246b99b7e6b2b558e677 Mon Sep 17 00:00:00 2001 From: Alan Donovan Date: Mon, 16 Dec 2024 13:57:28 -0500 Subject: [PATCH 23/73] internal/stdlib: re-generate manifest This happens as part of the 1.24 release, but it's good to do it for the RC as well, otherwise we fail to emit diagnostics for uses of go1.24 symbols such as testing.B.Loop because they are missing from the manifest, and the tool fails open. Change-Id: Ic44a71daffb837862bfe802ef318097709d8571e Reviewed-on: https://go-review.googlesource.com/c/tools/+/636835 Auto-Submit: Alan Donovan Reviewed-by: Robert Findley LUCI-TryBot-Result: Go LUCI --- internal/stdlib/manifest.go | 219 ++++++++++++++++++++++++++++++++++++ 1 file changed, 219 insertions(+) diff --git a/internal/stdlib/manifest.go b/internal/stdlib/manifest.go index cdaac9ab34d..9f0b871ff6b 100644 --- a/internal/stdlib/manifest.go +++ b/internal/stdlib/manifest.go @@ -268,6 +268,8 @@ var PackageSymbols = map[string][]Symbol{ {"ErrTooLarge", Var, 0}, {"Fields", Func, 0}, {"FieldsFunc", Func, 0}, + {"FieldsFuncSeq", Func, 24}, + {"FieldsSeq", Func, 24}, {"HasPrefix", Func, 0}, {"HasSuffix", Func, 0}, {"Index", Func, 0}, @@ -280,6 +282,7 @@ var PackageSymbols = map[string][]Symbol{ {"LastIndexAny", Func, 0}, {"LastIndexByte", Func, 5}, {"LastIndexFunc", Func, 0}, + {"Lines", Func, 24}, {"Map", Func, 0}, {"MinRead", Const, 0}, {"NewBuffer", Func, 0}, @@ -293,7 +296,9 @@ var PackageSymbols = map[string][]Symbol{ {"Split", Func, 0}, {"SplitAfter", Func, 0}, {"SplitAfterN", Func, 0}, + {"SplitAfterSeq", Func, 24}, {"SplitN", Func, 0}, + {"SplitSeq", Func, 24}, {"Title", Func, 0}, {"ToLower", Func, 0}, {"ToLowerSpecial", Func, 0}, @@ -535,6 +540,7 @@ var PackageSymbols = map[string][]Symbol{ {"NewCTR", Func, 0}, {"NewGCM", Func, 2}, {"NewGCMWithNonceSize", Func, 5}, + {"NewGCMWithRandomNonce", Func, 24}, {"NewGCMWithTagSize", Func, 11}, {"NewOFB", Func, 0}, {"Stream", Type, 0}, @@ -673,6 +679,14 @@ var PackageSymbols = map[string][]Symbol{ {"Unmarshal", Func, 0}, {"UnmarshalCompressed", Func, 15}, }, + "crypto/fips140": { + {"Enabled", Func, 24}, + }, + "crypto/hkdf": { + {"Expand", Func, 24}, + {"Extract", Func, 24}, + {"Key", Func, 24}, + }, "crypto/hmac": { {"Equal", Func, 1}, {"New", Func, 0}, @@ -683,11 +697,43 @@ var PackageSymbols = map[string][]Symbol{ {"Size", Const, 0}, {"Sum", Func, 2}, }, + "crypto/mlkem": { + {"(*DecapsulationKey1024).Bytes", Method, 24}, + {"(*DecapsulationKey1024).Decapsulate", Method, 24}, + {"(*DecapsulationKey1024).EncapsulationKey", Method, 24}, + {"(*DecapsulationKey768).Bytes", Method, 24}, + {"(*DecapsulationKey768).Decapsulate", Method, 24}, + {"(*DecapsulationKey768).EncapsulationKey", Method, 24}, + {"(*EncapsulationKey1024).Bytes", Method, 24}, + {"(*EncapsulationKey1024).Encapsulate", Method, 24}, + {"(*EncapsulationKey768).Bytes", Method, 24}, + {"(*EncapsulationKey768).Encapsulate", Method, 24}, + {"CiphertextSize1024", Const, 24}, + {"CiphertextSize768", Const, 24}, + {"DecapsulationKey1024", Type, 24}, + {"DecapsulationKey768", Type, 24}, + {"EncapsulationKey1024", Type, 24}, + {"EncapsulationKey768", Type, 24}, + {"EncapsulationKeySize1024", Const, 24}, + {"EncapsulationKeySize768", Const, 24}, + {"GenerateKey1024", Func, 24}, + {"GenerateKey768", Func, 24}, + {"NewDecapsulationKey1024", Func, 24}, + {"NewDecapsulationKey768", Func, 24}, + {"NewEncapsulationKey1024", Func, 24}, + {"NewEncapsulationKey768", Func, 24}, + {"SeedSize", Const, 24}, + {"SharedKeySize", Const, 24}, + }, + "crypto/pbkdf2": { + {"Key", Func, 24}, + }, "crypto/rand": { {"Int", Func, 0}, {"Prime", Func, 0}, {"Read", Func, 0}, {"Reader", Var, 0}, + {"Text", Func, 24}, }, "crypto/rc4": { {"(*Cipher).Reset", Method, 0}, @@ -766,6 +812,39 @@ var PackageSymbols = map[string][]Symbol{ {"Sum224", Func, 2}, {"Sum256", Func, 2}, }, + "crypto/sha3": { + {"(*SHA3).AppendBinary", Method, 24}, + {"(*SHA3).BlockSize", Method, 24}, + {"(*SHA3).MarshalBinary", Method, 24}, + {"(*SHA3).Reset", Method, 24}, + {"(*SHA3).Size", Method, 24}, + {"(*SHA3).Sum", Method, 24}, + {"(*SHA3).UnmarshalBinary", Method, 24}, + {"(*SHA3).Write", Method, 24}, + {"(*SHAKE).AppendBinary", Method, 24}, + {"(*SHAKE).BlockSize", Method, 24}, + {"(*SHAKE).MarshalBinary", Method, 24}, + {"(*SHAKE).Read", Method, 24}, + {"(*SHAKE).Reset", Method, 24}, + {"(*SHAKE).UnmarshalBinary", Method, 24}, + {"(*SHAKE).Write", Method, 24}, + {"New224", Func, 24}, + {"New256", Func, 24}, + {"New384", Func, 24}, + {"New512", Func, 24}, + {"NewCSHAKE128", Func, 24}, + {"NewCSHAKE256", Func, 24}, + {"NewSHAKE128", Func, 24}, + {"NewSHAKE256", Func, 24}, + {"SHA3", Type, 24}, + {"SHAKE", Type, 24}, + {"Sum224", Func, 24}, + {"Sum256", Func, 24}, + {"Sum384", Func, 24}, + {"Sum512", Func, 24}, + {"SumSHAKE128", Func, 24}, + {"SumSHAKE256", Func, 24}, + }, "crypto/sha512": { {"BlockSize", Const, 0}, {"New", Func, 0}, @@ -788,6 +867,7 @@ var PackageSymbols = map[string][]Symbol{ {"ConstantTimeEq", Func, 0}, {"ConstantTimeLessOrEq", Func, 2}, {"ConstantTimeSelect", Func, 0}, + {"WithDataIndependentTiming", Func, 24}, {"XORBytes", Func, 20}, }, "crypto/tls": { @@ -864,6 +944,7 @@ var PackageSymbols = map[string][]Symbol{ {"ClientHelloInfo", Type, 4}, {"ClientHelloInfo.CipherSuites", Field, 4}, {"ClientHelloInfo.Conn", Field, 8}, + {"ClientHelloInfo.Extensions", Field, 24}, {"ClientHelloInfo.ServerName", Field, 4}, {"ClientHelloInfo.SignatureSchemes", Field, 8}, {"ClientHelloInfo.SupportedCurves", Field, 4}, @@ -881,6 +962,7 @@ var PackageSymbols = map[string][]Symbol{ {"Config.CurvePreferences", Field, 3}, {"Config.DynamicRecordSizingDisabled", Field, 7}, {"Config.EncryptedClientHelloConfigList", Field, 23}, + {"Config.EncryptedClientHelloKeys", Field, 24}, {"Config.EncryptedClientHelloRejectionVerify", Field, 23}, {"Config.GetCertificate", Field, 4}, {"Config.GetClientCertificate", Field, 8}, @@ -934,6 +1016,10 @@ var PackageSymbols = map[string][]Symbol{ {"ECHRejectionError", Type, 23}, {"ECHRejectionError.RetryConfigList", Field, 23}, {"Ed25519", Const, 13}, + {"EncryptedClientHelloKey", Type, 24}, + {"EncryptedClientHelloKey.Config", Field, 24}, + {"EncryptedClientHelloKey.PrivateKey", Field, 24}, + {"EncryptedClientHelloKey.SendAsRetry", Field, 24}, {"InsecureCipherSuites", Func, 14}, {"Listen", Func, 0}, {"LoadX509KeyPair", Func, 0}, @@ -1032,6 +1118,7 @@ var PackageSymbols = map[string][]Symbol{ {"VersionTLS12", Const, 2}, {"VersionTLS13", Const, 12}, {"X25519", Const, 8}, + {"X25519MLKEM768", Const, 24}, {"X509KeyPair", Func, 0}, }, "crypto/x509": { @@ -1056,6 +1143,8 @@ var PackageSymbols = map[string][]Symbol{ {"(ConstraintViolationError).Error", Method, 0}, {"(HostnameError).Error", Method, 0}, {"(InsecureAlgorithmError).Error", Method, 6}, + {"(OID).AppendBinary", Method, 24}, + {"(OID).AppendText", Method, 24}, {"(OID).Equal", Method, 22}, {"(OID).EqualASN1OID", Method, 22}, {"(OID).MarshalBinary", Method, 23}, @@ -1084,6 +1173,10 @@ var PackageSymbols = map[string][]Symbol{ {"Certificate.Extensions", Field, 2}, {"Certificate.ExtraExtensions", Field, 2}, {"Certificate.IPAddresses", Field, 1}, + {"Certificate.InhibitAnyPolicy", Field, 24}, + {"Certificate.InhibitAnyPolicyZero", Field, 24}, + {"Certificate.InhibitPolicyMapping", Field, 24}, + {"Certificate.InhibitPolicyMappingZero", Field, 24}, {"Certificate.IsCA", Field, 0}, {"Certificate.Issuer", Field, 0}, {"Certificate.IssuingCertificateURL", Field, 2}, @@ -1100,6 +1193,7 @@ var PackageSymbols = map[string][]Symbol{ {"Certificate.PermittedURIDomains", Field, 10}, {"Certificate.Policies", Field, 22}, {"Certificate.PolicyIdentifiers", Field, 0}, + {"Certificate.PolicyMappings", Field, 24}, {"Certificate.PublicKey", Field, 0}, {"Certificate.PublicKeyAlgorithm", Field, 0}, {"Certificate.Raw", Field, 0}, @@ -1107,6 +1201,8 @@ var PackageSymbols = map[string][]Symbol{ {"Certificate.RawSubject", Field, 0}, {"Certificate.RawSubjectPublicKeyInfo", Field, 0}, {"Certificate.RawTBSCertificate", Field, 0}, + {"Certificate.RequireExplicitPolicy", Field, 24}, + {"Certificate.RequireExplicitPolicyZero", Field, 24}, {"Certificate.SerialNumber", Field, 0}, {"Certificate.Signature", Field, 0}, {"Certificate.SignatureAlgorithm", Field, 0}, @@ -1198,6 +1294,7 @@ var PackageSymbols = map[string][]Symbol{ {"NameConstraintsWithoutSANs", Const, 10}, {"NameMismatch", Const, 8}, {"NewCertPool", Func, 0}, + {"NoValidChains", Const, 24}, {"NotAuthorizedToSign", Const, 0}, {"OID", Type, 22}, {"OIDFromInts", Func, 22}, @@ -1219,6 +1316,9 @@ var PackageSymbols = map[string][]Symbol{ {"ParsePKCS8PrivateKey", Func, 0}, {"ParsePKIXPublicKey", Func, 0}, {"ParseRevocationList", Func, 19}, + {"PolicyMapping", Type, 24}, + {"PolicyMapping.IssuerDomainPolicy", Field, 24}, + {"PolicyMapping.SubjectDomainPolicy", Field, 24}, {"PublicKeyAlgorithm", Type, 0}, {"PureEd25519", Const, 13}, {"RSA", Const, 0}, @@ -1265,6 +1365,7 @@ var PackageSymbols = map[string][]Symbol{ {"UnknownPublicKeyAlgorithm", Const, 0}, {"UnknownSignatureAlgorithm", Const, 0}, {"VerifyOptions", Type, 0}, + {"VerifyOptions.CertificatePolicies", Field, 24}, {"VerifyOptions.CurrentTime", Field, 0}, {"VerifyOptions.DNSName", Field, 0}, {"VerifyOptions.Intermediates", Field, 0}, @@ -1975,6 +2076,8 @@ var PackageSymbols = map[string][]Symbol{ {"(*File).DynString", Method, 1}, {"(*File).DynValue", Method, 21}, {"(*File).DynamicSymbols", Method, 4}, + {"(*File).DynamicVersionNeeds", Method, 24}, + {"(*File).DynamicVersions", Method, 24}, {"(*File).ImportedLibraries", Method, 0}, {"(*File).ImportedSymbols", Method, 0}, {"(*File).Section", Method, 0}, @@ -2240,6 +2343,19 @@ var PackageSymbols = map[string][]Symbol{ {"DynFlag", Type, 0}, {"DynFlag1", Type, 21}, {"DynTag", Type, 0}, + {"DynamicVersion", Type, 24}, + {"DynamicVersion.Deps", Field, 24}, + {"DynamicVersion.Flags", Field, 24}, + {"DynamicVersion.Index", Field, 24}, + {"DynamicVersion.Name", Field, 24}, + {"DynamicVersionDep", Type, 24}, + {"DynamicVersionDep.Dep", Field, 24}, + {"DynamicVersionDep.Flags", Field, 24}, + {"DynamicVersionDep.Index", Field, 24}, + {"DynamicVersionFlag", Type, 24}, + {"DynamicVersionNeed", Type, 24}, + {"DynamicVersionNeed.Name", Field, 24}, + {"DynamicVersionNeed.Needs", Field, 24}, {"EI_ABIVERSION", Const, 0}, {"EI_CLASS", Const, 0}, {"EI_DATA", Const, 0}, @@ -3726,8 +3842,19 @@ var PackageSymbols = map[string][]Symbol{ {"Symbol.Size", Field, 0}, {"Symbol.Value", Field, 0}, {"Symbol.Version", Field, 13}, + {"Symbol.VersionIndex", Field, 24}, + {"Symbol.VersionScope", Field, 24}, + {"SymbolVersionScope", Type, 24}, {"Type", Type, 0}, + {"VER_FLG_BASE", Const, 24}, + {"VER_FLG_INFO", Const, 24}, + {"VER_FLG_WEAK", Const, 24}, {"Version", Type, 0}, + {"VersionScopeGlobal", Const, 24}, + {"VersionScopeHidden", Const, 24}, + {"VersionScopeLocal", Const, 24}, + {"VersionScopeNone", Const, 24}, + {"VersionScopeSpecific", Const, 24}, }, "debug/gosym": { {"(*DecodingError).Error", Method, 0}, @@ -4453,8 +4580,10 @@ var PackageSymbols = map[string][]Symbol{ {"FS", Type, 16}, }, "encoding": { + {"BinaryAppender", Type, 24}, {"BinaryMarshaler", Type, 2}, {"BinaryUnmarshaler", Type, 2}, + {"TextAppender", Type, 24}, {"TextMarshaler", Type, 2}, {"TextUnmarshaler", Type, 2}, }, @@ -5984,13 +6113,16 @@ var PackageSymbols = map[string][]Symbol{ {"(*Interface).Complete", Method, 5}, {"(*Interface).Embedded", Method, 5}, {"(*Interface).EmbeddedType", Method, 11}, + {"(*Interface).EmbeddedTypes", Method, 24}, {"(*Interface).Empty", Method, 5}, {"(*Interface).ExplicitMethod", Method, 5}, + {"(*Interface).ExplicitMethods", Method, 24}, {"(*Interface).IsComparable", Method, 18}, {"(*Interface).IsImplicit", Method, 18}, {"(*Interface).IsMethodSet", Method, 18}, {"(*Interface).MarkImplicit", Method, 18}, {"(*Interface).Method", Method, 5}, + {"(*Interface).Methods", Method, 24}, {"(*Interface).NumEmbeddeds", Method, 5}, {"(*Interface).NumExplicitMethods", Method, 5}, {"(*Interface).NumMethods", Method, 5}, @@ -6011,9 +6143,11 @@ var PackageSymbols = map[string][]Symbol{ {"(*MethodSet).At", Method, 5}, {"(*MethodSet).Len", Method, 5}, {"(*MethodSet).Lookup", Method, 5}, + {"(*MethodSet).Methods", Method, 24}, {"(*MethodSet).String", Method, 5}, {"(*Named).AddMethod", Method, 5}, {"(*Named).Method", Method, 5}, + {"(*Named).Methods", Method, 24}, {"(*Named).NumMethods", Method, 5}, {"(*Named).Obj", Method, 5}, {"(*Named).Origin", Method, 18}, @@ -6054,6 +6188,7 @@ var PackageSymbols = map[string][]Symbol{ {"(*Pointer).String", Method, 5}, {"(*Pointer).Underlying", Method, 5}, {"(*Scope).Child", Method, 5}, + {"(*Scope).Children", Method, 24}, {"(*Scope).Contains", Method, 5}, {"(*Scope).End", Method, 5}, {"(*Scope).Innermost", Method, 5}, @@ -6089,6 +6224,7 @@ var PackageSymbols = map[string][]Symbol{ {"(*StdSizes).Offsetsof", Method, 5}, {"(*StdSizes).Sizeof", Method, 5}, {"(*Struct).Field", Method, 5}, + {"(*Struct).Fields", Method, 24}, {"(*Struct).NumFields", Method, 5}, {"(*Struct).String", Method, 5}, {"(*Struct).Tag", Method, 5}, @@ -6100,8 +6236,10 @@ var PackageSymbols = map[string][]Symbol{ {"(*Tuple).Len", Method, 5}, {"(*Tuple).String", Method, 5}, {"(*Tuple).Underlying", Method, 5}, + {"(*Tuple).Variables", Method, 24}, {"(*TypeList).At", Method, 18}, {"(*TypeList).Len", Method, 18}, + {"(*TypeList).Types", Method, 24}, {"(*TypeName).Exported", Method, 5}, {"(*TypeName).Id", Method, 5}, {"(*TypeName).IsAlias", Method, 9}, @@ -6119,9 +6257,11 @@ var PackageSymbols = map[string][]Symbol{ {"(*TypeParam).Underlying", Method, 18}, {"(*TypeParamList).At", Method, 18}, {"(*TypeParamList).Len", Method, 18}, + {"(*TypeParamList).TypeParams", Method, 24}, {"(*Union).Len", Method, 18}, {"(*Union).String", Method, 18}, {"(*Union).Term", Method, 18}, + {"(*Union).Terms", Method, 24}, {"(*Union).Underlying", Method, 18}, {"(*Var).Anonymous", Method, 5}, {"(*Var).Embedded", Method, 11}, @@ -6392,10 +6532,12 @@ var PackageSymbols = map[string][]Symbol{ {"(*Hash).WriteByte", Method, 14}, {"(*Hash).WriteString", Method, 14}, {"Bytes", Func, 19}, + {"Comparable", Func, 24}, {"Hash", Type, 14}, {"MakeSeed", Func, 14}, {"Seed", Type, 14}, {"String", Func, 19}, + {"WriteComparable", Func, 24}, }, "html": { {"EscapeString", Func, 0}, @@ -7082,6 +7224,7 @@ var PackageSymbols = map[string][]Symbol{ {"(*JSONHandler).WithGroup", Method, 21}, {"(*Level).UnmarshalJSON", Method, 21}, {"(*Level).UnmarshalText", Method, 21}, + {"(*LevelVar).AppendText", Method, 24}, {"(*LevelVar).Level", Method, 21}, {"(*LevelVar).MarshalText", Method, 21}, {"(*LevelVar).Set", Method, 21}, @@ -7110,6 +7253,7 @@ var PackageSymbols = map[string][]Symbol{ {"(Attr).Equal", Method, 21}, {"(Attr).String", Method, 21}, {"(Kind).String", Method, 21}, + {"(Level).AppendText", Method, 24}, {"(Level).Level", Method, 21}, {"(Level).MarshalJSON", Method, 21}, {"(Level).MarshalText", Method, 21}, @@ -7140,6 +7284,7 @@ var PackageSymbols = map[string][]Symbol{ {"Debug", Func, 21}, {"DebugContext", Func, 21}, {"Default", Func, 21}, + {"DiscardHandler", Var, 24}, {"Duration", Func, 21}, {"DurationValue", Func, 21}, {"Error", Func, 21}, @@ -7375,6 +7520,7 @@ var PackageSymbols = map[string][]Symbol{ {"(*Float).Acc", Method, 5}, {"(*Float).Add", Method, 5}, {"(*Float).Append", Method, 5}, + {"(*Float).AppendText", Method, 24}, {"(*Float).Cmp", Method, 5}, {"(*Float).Copy", Method, 5}, {"(*Float).Float32", Method, 5}, @@ -7421,6 +7567,7 @@ var PackageSymbols = map[string][]Symbol{ {"(*Int).And", Method, 0}, {"(*Int).AndNot", Method, 0}, {"(*Int).Append", Method, 6}, + {"(*Int).AppendText", Method, 24}, {"(*Int).Binomial", Method, 0}, {"(*Int).Bit", Method, 0}, {"(*Int).BitLen", Method, 0}, @@ -7477,6 +7624,7 @@ var PackageSymbols = map[string][]Symbol{ {"(*Int).Xor", Method, 0}, {"(*Rat).Abs", Method, 0}, {"(*Rat).Add", Method, 0}, + {"(*Rat).AppendText", Method, 24}, {"(*Rat).Cmp", Method, 0}, {"(*Rat).Denom", Method, 0}, {"(*Rat).Float32", Method, 4}, @@ -7659,11 +7807,13 @@ var PackageSymbols = map[string][]Symbol{ {"Zipf", Type, 0}, }, "math/rand/v2": { + {"(*ChaCha8).AppendBinary", Method, 24}, {"(*ChaCha8).MarshalBinary", Method, 22}, {"(*ChaCha8).Read", Method, 23}, {"(*ChaCha8).Seed", Method, 22}, {"(*ChaCha8).Uint64", Method, 22}, {"(*ChaCha8).UnmarshalBinary", Method, 22}, + {"(*PCG).AppendBinary", Method, 24}, {"(*PCG).MarshalBinary", Method, 22}, {"(*PCG).Seed", Method, 22}, {"(*PCG).Uint64", Method, 22}, @@ -7931,6 +8081,7 @@ var PackageSymbols = map[string][]Symbol{ {"(*UnixListener).SyscallConn", Method, 10}, {"(Flags).String", Method, 0}, {"(HardwareAddr).String", Method, 0}, + {"(IP).AppendText", Method, 24}, {"(IP).DefaultMask", Method, 0}, {"(IP).Equal", Method, 0}, {"(IP).IsGlobalUnicast", Method, 0}, @@ -8131,6 +8282,9 @@ var PackageSymbols = map[string][]Symbol{ {"(*MaxBytesError).Error", Method, 19}, {"(*ProtocolError).Error", Method, 0}, {"(*ProtocolError).Is", Method, 21}, + {"(*Protocols).SetHTTP1", Method, 24}, + {"(*Protocols).SetHTTP2", Method, 24}, + {"(*Protocols).SetUnencryptedHTTP2", Method, 24}, {"(*Request).AddCookie", Method, 0}, {"(*Request).BasicAuth", Method, 4}, {"(*Request).Clone", Method, 13}, @@ -8190,6 +8344,10 @@ var PackageSymbols = map[string][]Symbol{ {"(Header).Values", Method, 14}, {"(Header).Write", Method, 0}, {"(Header).WriteSubset", Method, 0}, + {"(Protocols).HTTP1", Method, 24}, + {"(Protocols).HTTP2", Method, 24}, + {"(Protocols).String", Method, 24}, + {"(Protocols).UnencryptedHTTP2", Method, 24}, {"AllowQuerySemicolons", Func, 17}, {"CanonicalHeaderKey", Func, 0}, {"Client", Type, 0}, @@ -8252,6 +8410,18 @@ var PackageSymbols = map[string][]Symbol{ {"FileSystem", Type, 0}, {"Flusher", Type, 0}, {"Get", Func, 0}, + {"HTTP2Config", Type, 24}, + {"HTTP2Config.CountError", Field, 24}, + {"HTTP2Config.MaxConcurrentStreams", Field, 24}, + {"HTTP2Config.MaxDecoderHeaderTableSize", Field, 24}, + {"HTTP2Config.MaxEncoderHeaderTableSize", Field, 24}, + {"HTTP2Config.MaxReadFrameSize", Field, 24}, + {"HTTP2Config.MaxReceiveBufferPerConnection", Field, 24}, + {"HTTP2Config.MaxReceiveBufferPerStream", Field, 24}, + {"HTTP2Config.PermitProhibitedCipherSuites", Field, 24}, + {"HTTP2Config.PingTimeout", Field, 24}, + {"HTTP2Config.SendPingTimeout", Field, 24}, + {"HTTP2Config.WriteByteTimeout", Field, 24}, {"Handle", Func, 0}, {"HandleFunc", Func, 0}, {"Handler", Type, 0}, @@ -8292,6 +8462,7 @@ var PackageSymbols = map[string][]Symbol{ {"PostForm", Func, 0}, {"ProtocolError", Type, 0}, {"ProtocolError.ErrorString", Field, 0}, + {"Protocols", Type, 24}, {"ProxyFromEnvironment", Func, 0}, {"ProxyURL", Func, 0}, {"PushOptions", Type, 8}, @@ -8361,9 +8532,11 @@ var PackageSymbols = map[string][]Symbol{ {"Server.ConnState", Field, 3}, {"Server.DisableGeneralOptionsHandler", Field, 20}, {"Server.ErrorLog", Field, 3}, + {"Server.HTTP2", Field, 24}, {"Server.Handler", Field, 0}, {"Server.IdleTimeout", Field, 8}, {"Server.MaxHeaderBytes", Field, 0}, + {"Server.Protocols", Field, 24}, {"Server.ReadHeaderTimeout", Field, 8}, {"Server.ReadTimeout", Field, 0}, {"Server.TLSConfig", Field, 0}, @@ -8453,12 +8626,14 @@ var PackageSymbols = map[string][]Symbol{ {"Transport.ExpectContinueTimeout", Field, 6}, {"Transport.ForceAttemptHTTP2", Field, 13}, {"Transport.GetProxyConnectHeader", Field, 16}, + {"Transport.HTTP2", Field, 24}, {"Transport.IdleConnTimeout", Field, 7}, {"Transport.MaxConnsPerHost", Field, 11}, {"Transport.MaxIdleConns", Field, 7}, {"Transport.MaxIdleConnsPerHost", Field, 0}, {"Transport.MaxResponseHeaderBytes", Field, 7}, {"Transport.OnProxyConnectResponse", Field, 20}, + {"Transport.Protocols", Field, 24}, {"Transport.Proxy", Field, 0}, {"Transport.ProxyConnectHeader", Field, 8}, {"Transport.ReadBufferSize", Field, 13}, @@ -8646,6 +8821,8 @@ var PackageSymbols = map[string][]Symbol{ {"(*AddrPort).UnmarshalText", Method, 18}, {"(*Prefix).UnmarshalBinary", Method, 18}, {"(*Prefix).UnmarshalText", Method, 18}, + {"(Addr).AppendBinary", Method, 24}, + {"(Addr).AppendText", Method, 24}, {"(Addr).AppendTo", Method, 18}, {"(Addr).As16", Method, 18}, {"(Addr).As4", Method, 18}, @@ -8676,6 +8853,8 @@ var PackageSymbols = map[string][]Symbol{ {"(Addr).WithZone", Method, 18}, {"(Addr).Zone", Method, 18}, {"(AddrPort).Addr", Method, 18}, + {"(AddrPort).AppendBinary", Method, 24}, + {"(AddrPort).AppendText", Method, 24}, {"(AddrPort).AppendTo", Method, 18}, {"(AddrPort).Compare", Method, 22}, {"(AddrPort).IsValid", Method, 18}, @@ -8684,6 +8863,8 @@ var PackageSymbols = map[string][]Symbol{ {"(AddrPort).Port", Method, 18}, {"(AddrPort).String", Method, 18}, {"(Prefix).Addr", Method, 18}, + {"(Prefix).AppendBinary", Method, 24}, + {"(Prefix).AppendText", Method, 24}, {"(Prefix).AppendTo", Method, 18}, {"(Prefix).Bits", Method, 18}, {"(Prefix).Contains", Method, 18}, @@ -8868,6 +9049,7 @@ var PackageSymbols = map[string][]Symbol{ {"(*Error).Temporary", Method, 6}, {"(*Error).Timeout", Method, 6}, {"(*Error).Unwrap", Method, 13}, + {"(*URL).AppendBinary", Method, 24}, {"(*URL).EscapedFragment", Method, 15}, {"(*URL).EscapedPath", Method, 5}, {"(*URL).Hostname", Method, 8}, @@ -8967,6 +9149,17 @@ var PackageSymbols = map[string][]Symbol{ {"(*ProcessState).SysUsage", Method, 0}, {"(*ProcessState).SystemTime", Method, 0}, {"(*ProcessState).UserTime", Method, 0}, + {"(*Root).Close", Method, 24}, + {"(*Root).Create", Method, 24}, + {"(*Root).FS", Method, 24}, + {"(*Root).Lstat", Method, 24}, + {"(*Root).Mkdir", Method, 24}, + {"(*Root).Name", Method, 24}, + {"(*Root).Open", Method, 24}, + {"(*Root).OpenFile", Method, 24}, + {"(*Root).OpenRoot", Method, 24}, + {"(*Root).Remove", Method, 24}, + {"(*Root).Stat", Method, 24}, {"(*SyscallError).Error", Method, 0}, {"(*SyscallError).Timeout", Method, 10}, {"(*SyscallError).Unwrap", Method, 13}, @@ -9060,6 +9253,8 @@ var PackageSymbols = map[string][]Symbol{ {"O_WRONLY", Const, 0}, {"Open", Func, 0}, {"OpenFile", Func, 0}, + {"OpenInRoot", Func, 24}, + {"OpenRoot", Func, 24}, {"PathError", Type, 0}, {"PathError.Err", Field, 0}, {"PathError.Op", Field, 0}, @@ -9081,6 +9276,7 @@ var PackageSymbols = map[string][]Symbol{ {"Remove", Func, 0}, {"RemoveAll", Func, 0}, {"Rename", Func, 0}, + {"Root", Type, 24}, {"SEEK_CUR", Const, 0}, {"SEEK_END", Const, 0}, {"SEEK_SET", Const, 0}, @@ -9422,6 +9618,7 @@ var PackageSymbols = map[string][]Symbol{ {"Zero", Func, 0}, }, "regexp": { + {"(*Regexp).AppendText", Method, 24}, {"(*Regexp).Copy", Method, 6}, {"(*Regexp).Expand", Method, 0}, {"(*Regexp).ExpandString", Method, 0}, @@ -9602,6 +9799,8 @@ var PackageSymbols = map[string][]Symbol{ {"(*StackRecord).Stack", Method, 0}, {"(*TypeAssertionError).Error", Method, 0}, {"(*TypeAssertionError).RuntimeError", Method, 0}, + {"(Cleanup).Stop", Method, 24}, + {"AddCleanup", Func, 24}, {"BlockProfile", Func, 1}, {"BlockProfileRecord", Type, 1}, {"BlockProfileRecord.Count", Field, 1}, @@ -9612,6 +9811,7 @@ var PackageSymbols = map[string][]Symbol{ {"Caller", Func, 0}, {"Callers", Func, 0}, {"CallersFrames", Func, 7}, + {"Cleanup", Type, 24}, {"Compiler", Const, 0}, {"Error", Type, 0}, {"Frame", Type, 7}, @@ -9974,6 +10174,8 @@ var PackageSymbols = map[string][]Symbol{ {"EqualFold", Func, 0}, {"Fields", Func, 0}, {"FieldsFunc", Func, 0}, + {"FieldsFuncSeq", Func, 24}, + {"FieldsSeq", Func, 24}, {"HasPrefix", Func, 0}, {"HasSuffix", Func, 0}, {"Index", Func, 0}, @@ -9986,6 +10188,7 @@ var PackageSymbols = map[string][]Symbol{ {"LastIndexAny", Func, 0}, {"LastIndexByte", Func, 5}, {"LastIndexFunc", Func, 0}, + {"Lines", Func, 24}, {"Map", Func, 0}, {"NewReader", Func, 0}, {"NewReplacer", Func, 0}, @@ -9997,7 +10200,9 @@ var PackageSymbols = map[string][]Symbol{ {"Split", Func, 0}, {"SplitAfter", Func, 0}, {"SplitAfterN", Func, 0}, + {"SplitAfterSeq", Func, 24}, {"SplitN", Func, 0}, + {"SplitSeq", Func, 24}, {"Title", Func, 0}, {"ToLower", Func, 0}, {"ToLowerSpecial", Func, 0}, @@ -16413,7 +16618,9 @@ var PackageSymbols = map[string][]Symbol{ {"ValueOf", Func, 0}, }, "testing": { + {"(*B).Chdir", Method, 24}, {"(*B).Cleanup", Method, 14}, + {"(*B).Context", Method, 24}, {"(*B).Elapsed", Method, 20}, {"(*B).Error", Method, 0}, {"(*B).Errorf", Method, 0}, @@ -16425,6 +16632,7 @@ var PackageSymbols = map[string][]Symbol{ {"(*B).Helper", Method, 9}, {"(*B).Log", Method, 0}, {"(*B).Logf", Method, 0}, + {"(*B).Loop", Method, 24}, {"(*B).Name", Method, 8}, {"(*B).ReportAllocs", Method, 1}, {"(*B).ReportMetric", Method, 13}, @@ -16442,7 +16650,9 @@ var PackageSymbols = map[string][]Symbol{ {"(*B).StopTimer", Method, 0}, {"(*B).TempDir", Method, 15}, {"(*F).Add", Method, 18}, + {"(*F).Chdir", Method, 24}, {"(*F).Cleanup", Method, 18}, + {"(*F).Context", Method, 24}, {"(*F).Error", Method, 18}, {"(*F).Errorf", Method, 18}, {"(*F).Fail", Method, 18}, @@ -16463,7 +16673,9 @@ var PackageSymbols = map[string][]Symbol{ {"(*F).TempDir", Method, 18}, {"(*M).Run", Method, 4}, {"(*PB).Next", Method, 3}, + {"(*T).Chdir", Method, 24}, {"(*T).Cleanup", Method, 14}, + {"(*T).Context", Method, 24}, {"(*T).Deadline", Method, 15}, {"(*T).Error", Method, 0}, {"(*T).Errorf", Method, 0}, @@ -16954,7 +17166,9 @@ var PackageSymbols = map[string][]Symbol{ {"(Time).Add", Method, 0}, {"(Time).AddDate", Method, 0}, {"(Time).After", Method, 0}, + {"(Time).AppendBinary", Method, 24}, {"(Time).AppendFormat", Method, 5}, + {"(Time).AppendText", Method, 24}, {"(Time).Before", Method, 0}, {"(Time).Clock", Method, 0}, {"(Time).Compare", Method, 20}, @@ -17428,4 +17642,9 @@ var PackageSymbols = map[string][]Symbol{ {"String", Func, 0}, {"StringData", Func, 0}, }, + "weak": { + {"(Pointer).Value", Method, 24}, + {"Make", Func, 24}, + {"Pointer", Type, 24}, + }, } From bf42cd7fd640b89236a81947ba84bcee6700d7a1 Mon Sep 17 00:00:00 2001 From: Michael Matloob Date: Fri, 13 Dec 2024 13:05:33 -0500 Subject: [PATCH 24/73] go/packages: add Target field and NeedTarget LoadMode bit Adding the NeedTarget LoadMode bit will populate the Target field on a packages.Package with the Target from the driver. The Target field is populated with the install path to the .a file for libraries and the executable file for main packages. In module mode there usually isn't an install target for .a files (before Go 1.20, .a files for std were installed in $GOROOT/pkg, but Go 1.20 stopped installing them, though this behavior can be changed with the installgoroot GODEBUG setting). That means the target is primarily useful for main packages or in GOPATH mode. There isn't much additional work that needs to be done on the go command side to determine the target, if there will be one, once the name and files of the package are determined. One alternative to adding a NeedTarget bit would then be to reuse one of the previously existing bits to produce Target. But neither NeedFiles or NeedName's meanings are a good fit for Target so this CL adds a new bit. Fixes golang/go#38445 Change-Id: I718ec22ff1f82a672449c586b007398714b8c10e Reviewed-on: https://go-review.googlesource.com/c/tools/+/635778 LUCI-TryBot-Result: Go LUCI Reviewed-by: Robert Findley --- go/packages/golist.go | 5 ++++ go/packages/loadmode_string.go | 1 + go/packages/packages.go | 7 +++++ go/packages/packages_test.go | 55 +++++++++++++++++++++++++++++++--- 4 files changed, 64 insertions(+), 4 deletions(-) diff --git a/go/packages/golist.go b/go/packages/golist.go index 870271ed51f..0458b4f9c43 100644 --- a/go/packages/golist.go +++ b/go/packages/golist.go @@ -322,6 +322,7 @@ type jsonPackage struct { ImportPath string Dir string Name string + Target string Export string GoFiles []string CompiledGoFiles []string @@ -506,6 +507,7 @@ func (state *golistState) createDriverResponse(words ...string) (*DriverResponse Name: p.Name, ID: p.ImportPath, Dir: p.Dir, + Target: p.Target, GoFiles: absJoin(p.Dir, p.GoFiles, p.CgoFiles), CompiledGoFiles: absJoin(p.Dir, p.CompiledGoFiles), OtherFiles: absJoin(p.Dir, otherFiles(p)...), @@ -811,6 +813,9 @@ func jsonFlag(cfg *Config, goVersion int) string { if cfg.Mode&NeedEmbedPatterns != 0 { addFields("EmbedPatterns") } + if cfg.Mode&NeedTarget != 0 { + addFields("Target") + } return "-json=" + strings.Join(fields, ",") } diff --git a/go/packages/loadmode_string.go b/go/packages/loadmode_string.go index 969da4c263c..69eec9f44dd 100644 --- a/go/packages/loadmode_string.go +++ b/go/packages/loadmode_string.go @@ -27,6 +27,7 @@ var modes = [...]struct { {NeedModule, "NeedModule"}, {NeedEmbedFiles, "NeedEmbedFiles"}, {NeedEmbedPatterns, "NeedEmbedPatterns"}, + {NeedTarget, "NeedTarget"}, } func (mode LoadMode) String() string { diff --git a/go/packages/packages.go b/go/packages/packages.go index 9dedf9777dc..0147d9080aa 100644 --- a/go/packages/packages.go +++ b/go/packages/packages.go @@ -118,6 +118,9 @@ const ( // NeedEmbedPatterns adds EmbedPatterns. NeedEmbedPatterns + // NeedTarget adds Target. + NeedTarget + // Be sure to update loadmode_string.go when adding new items! ) @@ -479,6 +482,10 @@ type Package struct { // information for the package as provided by the build system. ExportFile string + // Target is the absolute install path of the .a file, for libraries, + // and of the executable file, for binaries. + Target string + // Imports maps import paths appearing in the package's Go source files // to corresponding loaded Packages. Imports map[string]*Package diff --git a/go/packages/packages_test.go b/go/packages/packages_test.go index b52be337ee2..7f6181c27fe 100644 --- a/go/packages/packages_test.go +++ b/go/packages/packages_test.go @@ -2322,8 +2322,8 @@ func TestLoadModeStrings(t *testing.T) { "(NeedName|NeedExportFile)", }, { - packages.NeedForTest | packages.NeedEmbedFiles | packages.NeedEmbedPatterns, - "(NeedForTest|NeedEmbedFiles|NeedEmbedPatterns)", + packages.NeedForTest | packages.NeedTarget | packages.NeedEmbedFiles | packages.NeedEmbedPatterns, + "(NeedForTest|NeedEmbedFiles|NeedEmbedPatterns|NeedTarget)", }, { packages.NeedName | packages.NeedFiles | packages.NeedCompiledGoFiles | packages.NeedImports | packages.NeedDeps | packages.NeedExportFile | packages.NeedTypes | packages.NeedSyntax | packages.NeedTypesInfo | packages.NeedTypesSizes, @@ -2334,8 +2334,8 @@ func TestLoadModeStrings(t *testing.T) { "(NeedName|NeedModule)", }, { - packages.NeedName | 0x10000, // off the end (future use) - "(NeedName|0x10000)", + packages.NeedName | 0x100000, // off the end (future use) + "(NeedName|0x100000)", }, { packages.NeedName | 0x400, // needInternalDepsErrors @@ -3339,6 +3339,53 @@ func Foo() int { return a.Foo() } t.Logf("Packages: %+v", pkgs) } +// TestTarget tests the new field added as part of golang/go#38445. +// The test uses GOPATH mode because non-main packages don't usually +// have install targets in module mode. +func TestTarget(t *testing.T) { + testenv.NeedsGoPackages(t) + + dir := writeTree(t, ` +-- gopath/src/a/a.go -- +package a + +func Foo() {} +-- gopath/src/b/b.go -- +package main + +import "a" + +func main() { + a.Foo() +} +`) + gopath := filepath.Join(dir, "gopath") + + pkgs, err := packages.Load(&packages.Config{ + Mode: packages.NeedName | packages.NeedTarget, + Env: []string{"GOPATH=" + gopath, "GO111MODULE=off"}, + }, filepath.Join(gopath, "src", "...")) + if err != nil { + t.Fatal(err) + } + var goexe string + if runtime.GOOS == "windows" { + goexe = ".exe" + } + want := map[string]string{ + "a": filepath.Join(gopath, "pkg", runtime.GOOS+"_"+runtime.GOARCH, "a.a"), + "b": filepath.Join(gopath, "bin", "b"+goexe), + } + got := make(map[string]string) + packages.Visit(pkgs, nil, func(pkg *packages.Package) { + got[pkg.PkgPath] = pkg.Target + }) + if diff := cmp.Diff(want, got); diff != "" { + t.Errorf("Load returned mismatching Target fields (pkgpath->target -want +got):\n%s", diff) + } + t.Logf("Packages: %+v", pkgs) +} + // TestMainPackagePathInModeTypes tests (*types.Package).Path() for // main packages in mode NeedTypes, a regression test for #70742, a // bug in cmd/compile's export data that caused them to appear as From d3a1606bbb55ef1d02bc2148e78dffe40d1e28eb Mon Sep 17 00:00:00 2001 From: Madeline Kalil Date: Mon, 16 Dec 2024 17:01:45 -0500 Subject: [PATCH 25/73] gopls/internal/golang: definition support return statements Enables you to use the definition operation on a return token and brings you to the return parameters. Updates golang/go#70462 Change-Id: I080ff37aad5ac1498e320429fad6dd4b043f02c8 Reviewed-on: https://go-review.googlesource.com/c/tools/+/636855 Auto-Submit: Madeline Kalil LUCI-TryBot-Result: Go LUCI Reviewed-by: Alan Donovan --- gopls/doc/features/navigation.md | 1 + gopls/doc/release/v0.18.0.md | 7 +++++ gopls/internal/golang/definition.go | 31 +++++++++++++++++-- .../marker/testdata/definition/return.txt | 23 ++++++++++++++ 4 files changed, 60 insertions(+), 2 deletions(-) create mode 100644 gopls/internal/test/marker/testdata/definition/return.txt diff --git a/gopls/doc/features/navigation.md b/gopls/doc/features/navigation.md index e2a4954f5c0..e744c341910 100644 --- a/gopls/doc/features/navigation.md +++ b/gopls/doc/features/navigation.md @@ -24,6 +24,7 @@ A definition query also works in these unexpected places: it returns the location of the embedded file. - On the declaration of a non-Go function (a `func` with no body), it returns the location of the assembly implementation, if any, +- On a **return statement**, it returns the location of the function's result variables. -## `gc_details`: Toggle display of Go compiler optimization decisions - - -This codelens source causes the `package` declaration of -each file to be annotated with a command to toggle the -state of the per-session variable that controls whether -optimization decisions from the Go compiler (formerly known -as "gc") should be displayed as diagnostics. - -Optimization decisions include: -- whether a variable escapes, and how escape is inferred; -- whether a nil-pointer check is implied or eliminated; -- whether a function can be inlined. - -TODO(adonovan): this source is off by default because the -annotation is annoying and because VS Code has a separate -"Toggle gc details" command. Replace it with a Code Action -("Source action..."). - - -Default: off - -File type: Go - ## `generate`: Run `go generate` diff --git a/gopls/doc/features/diagnostics.md b/gopls/doc/features/diagnostics.md index d7b0911f308..09b3cc33e90 100644 --- a/gopls/doc/features/diagnostics.md +++ b/gopls/doc/features/diagnostics.md @@ -49,6 +49,26 @@ build`. Gopls doesn't actually run the compiler; that would be too The example above shows a `printf` formatting mistake. The diagnostic contains a link to the documentation for the `printf` analyzer. +There is an optional third source of diagnostics: + + + +- **Compiler optimization details** are diagnostics that report + details relevant to optimization decisions made by the Go + compiler, such as whether a variable escapes or a slice index + requires a bounds check. + + Optimization decisions include: + whether a variable escapes, and how escape is inferred; + whether a nil-pointer check is implied or eliminated; and + whether a function can be inlined. + + This source is disabled by default but can be enabled on a + package-by-package basis by invoking the + `source.toggleCompilerOptDetails` ("Toggle compiler optimization + details") code action. + + ## Recomputation of diagnostics By default, diagnostics are automatically recomputed each time the source files diff --git a/gopls/doc/features/transformation.md b/gopls/doc/features/transformation.md index 37015da2983..caf13221cfa 100644 --- a/gopls/doc/features/transformation.md +++ b/gopls/doc/features/transformation.md @@ -70,6 +70,7 @@ Gopls supports the following code actions: - [`source.freesymbols`](web.md#freesymbols) - `source.test` (undocumented) - [`source.addTest`](#source.addTest) +- [`source.toggleCompilerOptDetails`](diagnostics.md#toggleCompilerOptDetails) - [`gopls.doc.features`](README.md), which opens gopls' index of features in a browser - [`refactor.extract.constant`](#extract) - [`refactor.extract.function`](#extract) diff --git a/gopls/doc/release/v0.18.0.md b/gopls/doc/release/v0.18.0.md index afca6e2d924..2337b99e657 100644 --- a/gopls/doc/release/v0.18.0.md +++ b/gopls/doc/release/v0.18.0.md @@ -2,8 +2,24 @@ - The experimental `hoverKind=Structured` setting is no longer supported. +- The `gc_details` code lens has been deleted. (It was previously + disabled by default.) This functionality is now available through + the `settings.toggleCompilerOptDetails` code action (documented + below), as code actions are better supported than code lenses across + a range of clients. + + VS Code's special "Go: Toggle GC details" command continues to work. + # New features +## "Toggle compiler optimization details" code action + +This code action, accessible through the "Source Action" menu in VS +Code, toggles a per-package flag that causes Go compiler optimization +details to be reported as diagnostics. For example, it indicates which +variables escape to the heap, and which array accesses require bounds +checks. + ## New `modernize` analyzer Gopls will now report when code could be simplified or clarified by diff --git a/gopls/doc/settings.md b/gopls/doc/settings.md index 81134fe0950..d1be214e933 100644 --- a/gopls/doc/settings.md +++ b/gopls/doc/settings.md @@ -184,13 +184,12 @@ Example Usage: ... "codelenses": { "generate": false, // Don't show the `go generate` lens. - "gc_details": true // Show a code lens toggling the display of gc's choices. } ... } ``` -Default: `{"gc_details":false,"generate":true,"regenerate_cgo":true,"run_govulncheck":false,"tidy":true,"upgrade_dependency":true,"vendor":true}`. +Default: `{"generate":true,"regenerate_cgo":true,"run_govulncheck":false,"tidy":true,"upgrade_dependency":true,"vendor":true}`. ### `semanticTokens bool` @@ -321,8 +320,10 @@ Default: `false`. **This setting is experimental and may be deleted.** -annotations specifies the various kinds of optimization diagnostics -that should be reported by the gc_details command. +annotations specifies the various kinds of compiler +optimization details that should be reported as diagnostics +when enabled for a package by the "Toggle compiler +optimization details" (`gopls.gc_details`) command. Each enum must be one of: diff --git a/gopls/internal/cache/diagnostics.go b/gopls/internal/cache/diagnostics.go index 0adbcb495db..68c1632594f 100644 --- a/gopls/internal/cache/diagnostics.go +++ b/gopls/internal/cache/diagnostics.go @@ -95,21 +95,24 @@ func (d *Diagnostic) Hash() file.Hash { return hash } +// A DiagnosticSource identifies the source of a diagnostic. +// +// Its value may be one of the distinguished string values below, or +// the Name of an [analysis.Analyzer]. type DiagnosticSource string const ( - UnknownError DiagnosticSource = "" - ListError DiagnosticSource = "go list" - ParseError DiagnosticSource = "syntax" - TypeError DiagnosticSource = "compiler" - ModTidyError DiagnosticSource = "go mod tidy" - OptimizationDetailsError DiagnosticSource = "optimizer details" - UpgradeNotification DiagnosticSource = "upgrade available" - Vulncheck DiagnosticSource = "vulncheck imports" - Govulncheck DiagnosticSource = "govulncheck" - TemplateError DiagnosticSource = "template" - WorkFileError DiagnosticSource = "go.work file" - ConsistencyInfo DiagnosticSource = "consistency" + UnknownError DiagnosticSource = "" + ListError DiagnosticSource = "go list" + ParseError DiagnosticSource = "syntax" + TypeError DiagnosticSource = "compiler" + ModTidyError DiagnosticSource = "go mod tidy" + CompilerOptDetailsInfo DiagnosticSource = "optimizer details" // cmd/compile -json=0,dir + UpgradeNotification DiagnosticSource = "upgrade available" + Vulncheck DiagnosticSource = "vulncheck imports" + Govulncheck DiagnosticSource = "govulncheck" + TemplateError DiagnosticSource = "template" + WorkFileError DiagnosticSource = "go.work file" ) // A SuggestedFix represents a suggested fix (for a diagnostic) diff --git a/gopls/internal/cache/snapshot.go b/gopls/internal/cache/snapshot.go index d328ede26b7..de4a52ff6cb 100644 --- a/gopls/internal/cache/snapshot.go +++ b/gopls/internal/cache/snapshot.go @@ -183,9 +183,9 @@ type Snapshot struct { // vulns maps each go.mod file's URI to its known vulnerabilities. vulns *persistent.Map[protocol.DocumentURI, *vulncheck.Result] - // gcOptimizationDetails describes the packages for which we want - // optimization details to be included in the diagnostics. - gcOptimizationDetails map[metadata.PackageID]unit + // compilerOptDetails describes the packages for which we want + // compiler optimization details to be included in the diagnostics. + compilerOptDetails map[metadata.PackageID]unit // Concurrent type checking: // typeCheckMu guards the ongoing type checking batch, and reference count of @@ -1492,7 +1492,7 @@ func (s *Snapshot) clone(ctx, bgCtx context.Context, changed StateChange, done f // TODO(rfindley): reorganize this function to make the derivation of // needsDiagnosis clearer. - needsDiagnosis := len(changed.GCDetails) > 0 || len(changed.ModuleUpgrades) > 0 || len(changed.Vulns) > 0 + needsDiagnosis := len(changed.CompilerOptDetails) > 0 || len(changed.ModuleUpgrades) > 0 || len(changed.Vulns) > 0 bgCtx, cancel := context.WithCancel(bgCtx) result := &Snapshot{ @@ -1522,22 +1522,22 @@ func (s *Snapshot) clone(ctx, bgCtx context.Context, changed StateChange, done f vulns: cloneWith(s.vulns, changed.Vulns), } - // Compute the new set of packages for which we want gc details, after - // applying changed.GCDetails. - if len(s.gcOptimizationDetails) > 0 || len(changed.GCDetails) > 0 { - newGCDetails := make(map[metadata.PackageID]unit) - for id := range s.gcOptimizationDetails { - if _, ok := changed.GCDetails[id]; !ok { - newGCDetails[id] = unit{} // no change + // Compute the new set of packages for which we want compiler + // optimization details, after applying changed.CompilerOptDetails. + if len(s.compilerOptDetails) > 0 || len(changed.CompilerOptDetails) > 0 { + newCompilerOptDetails := make(map[metadata.PackageID]unit) + for id := range s.compilerOptDetails { + if _, ok := changed.CompilerOptDetails[id]; !ok { + newCompilerOptDetails[id] = unit{} // no change } } - for id, want := range changed.GCDetails { + for id, want := range changed.CompilerOptDetails { if want { - newGCDetails[id] = unit{} + newCompilerOptDetails[id] = unit{} } } - if len(newGCDetails) > 0 { - result.gcOptimizationDetails = newGCDetails + if len(newCompilerOptDetails) > 0 { + result.compilerOptDetails = newCompilerOptDetails } } @@ -2161,10 +2161,10 @@ func (s *Snapshot) setBuiltin(path string) { s.builtin = protocol.URIFromPath(path) } -// WantGCDetails reports whether to compute GC optimization details for the -// specified package. -func (s *Snapshot) WantGCDetails(id metadata.PackageID) bool { - _, ok := s.gcOptimizationDetails[id] +// WantCompilerOptDetails reports whether to compute compiler +// optimization details for the specified package. +func (s *Snapshot) WantCompilerOptDetails(id metadata.PackageID) bool { + _, ok := s.compilerOptDetails[id] return ok } diff --git a/gopls/internal/cache/view.go b/gopls/internal/cache/view.go index 030a58e6d8e..5fb03cb1152 100644 --- a/gopls/internal/cache/view.go +++ b/gopls/internal/cache/view.go @@ -736,11 +736,11 @@ func (s *Snapshot) initialize(ctx context.Context, firstAttempt bool) { // By far the most common of these is a change to file state, but a query of // module upgrade information or vulnerabilities also affects gopls' behavior. type StateChange struct { - Modifications []file.Modification // if set, the raw modifications originating this change - Files map[protocol.DocumentURI]file.Handle - ModuleUpgrades map[protocol.DocumentURI]map[string]string - Vulns map[protocol.DocumentURI]*vulncheck.Result - GCDetails map[metadata.PackageID]bool // package -> whether or not we want details + Modifications []file.Modification // if set, the raw modifications originating this change + Files map[protocol.DocumentURI]file.Handle + ModuleUpgrades map[protocol.DocumentURI]map[string]string + Vulns map[protocol.DocumentURI]*vulncheck.Result + CompilerOptDetails map[metadata.PackageID]bool // package -> whether or not we want details } // InvalidateView processes the provided state change, invalidating any derived diff --git a/gopls/internal/cmd/integration_test.go b/gopls/internal/cmd/integration_test.go index ad08119d397..d819279d699 100644 --- a/gopls/internal/cmd/integration_test.go +++ b/gopls/internal/cmd/integration_test.go @@ -994,6 +994,8 @@ type C struct{} res.checkExit(true) got := res.stdout want := `command "Browse documentation for package a" [source.doc]` + + "\n" + + `command "Toggle compiler optimization details" [source.toggleCompilerOptDetails]` + "\n" if got != want { t.Errorf("codeaction: got <<%s>>, want <<%s>>\nstderr:\n%s", got, want, res.stderr) diff --git a/gopls/internal/doc/api.json b/gopls/internal/doc/api.json index b7afce424eb..77afb52f8b9 100644 --- a/gopls/internal/doc/api.json +++ b/gopls/internal/doc/api.json @@ -638,7 +638,7 @@ { "Name": "annotations", "Type": "map[enum]bool", - "Doc": "annotations specifies the various kinds of optimization diagnostics\nthat should be reported by the gc_details command.\n", + "Doc": "annotations specifies the various kinds of compiler\noptimization details that should be reported as diagnostics\nwhen enabled for a package by the \"Toggle compiler\noptimization details\" (`gopls.gc_details`) command.\n", "EnumKeys": { "ValueType": "bool", "Keys": [ @@ -791,15 +791,10 @@ { "Name": "codelenses", "Type": "map[enum]bool", - "Doc": "codelenses overrides the enabled/disabled state of each of gopls'\nsources of [Code Lenses](codelenses.md).\n\nExample Usage:\n\n```json5\n\"gopls\": {\n...\n \"codelenses\": {\n \"generate\": false, // Don't show the `go generate` lens.\n \"gc_details\": true // Show a code lens toggling the display of gc's choices.\n }\n...\n}\n```\n", + "Doc": "codelenses overrides the enabled/disabled state of each of gopls'\nsources of [Code Lenses](codelenses.md).\n\nExample Usage:\n\n```json5\n\"gopls\": {\n...\n \"codelenses\": {\n \"generate\": false, // Don't show the `go generate` lens.\n }\n...\n}\n```\n", "EnumKeys": { "ValueType": "bool", "Keys": [ - { - "Name": "\"gc_details\"", - "Doc": "`\"gc_details\"`: Toggle display of Go compiler optimization decisions\n\nThis codelens source causes the `package` declaration of\neach file to be annotated with a command to toggle the\nstate of the per-session variable that controls whether\noptimization decisions from the Go compiler (formerly known\nas \"gc\") should be displayed as diagnostics.\n\nOptimization decisions include:\n- whether a variable escapes, and how escape is inferred;\n- whether a nil-pointer check is implied or eliminated;\n- whether a function can be inlined.\n\nTODO(adonovan): this source is off by default because the\nannotation is annoying and because VS Code has a separate\n\"Toggle gc details\" command. Replace it with a Code Action\n(\"Source action...\").\n", - "Default": "false" - }, { "Name": "\"generate\"", "Doc": "`\"generate\"`: Run `go generate`\n\nThis codelens source annotates any `//go:generate` comments\nwith commands to run `go generate` in this directory, on\nall directories recursively beneath this one.\n\nSee [Generating code](https://go.dev/blog/generate) for\nmore details.\n", @@ -843,7 +838,7 @@ ] }, "EnumValues": null, - "Default": "{\"gc_details\":false,\"generate\":true,\"regenerate_cgo\":true,\"run_govulncheck\":false,\"tidy\":true,\"upgrade_dependency\":true,\"vendor\":true}", + "Default": "{\"generate\":true,\"regenerate_cgo\":true,\"run_govulncheck\":false,\"tidy\":true,\"upgrade_dependency\":true,\"vendor\":true}", "Status": "", "Hierarchy": "ui" }, @@ -928,13 +923,6 @@ ] }, "Lenses": [ - { - "FileType": "Go", - "Lens": "gc_details", - "Title": "Toggle display of Go compiler optimization decisions", - "Doc": "\nThis codelens source causes the `package` declaration of\neach file to be annotated with a command to toggle the\nstate of the per-session variable that controls whether\noptimization decisions from the Go compiler (formerly known\nas \"gc\") should be displayed as diagnostics.\n\nOptimization decisions include:\n- whether a variable escapes, and how escape is inferred;\n- whether a nil-pointer check is implied or eliminated;\n- whether a function can be inlined.\n\nTODO(adonovan): this source is off by default because the\nannotation is annoying and because VS Code has a separate\n\"Toggle gc details\" command. Replace it with a Code Action\n(\"Source action...\").\n", - "Default": false - }, { "FileType": "Go", "Lens": "generate", diff --git a/gopls/internal/golang/code_lens.go b/gopls/internal/golang/code_lens.go index f0a5500b57f..1359d0d0148 100644 --- a/gopls/internal/golang/code_lens.go +++ b/gopls/internal/golang/code_lens.go @@ -23,10 +23,9 @@ import ( // CodeLensSources returns the supported sources of code lenses for Go files. func CodeLensSources() map[settings.CodeLensSource]cache.CodeLensSourceFunc { return map[settings.CodeLensSource]cache.CodeLensSourceFunc{ - settings.CodeLensGenerate: goGenerateCodeLens, // commands: Generate - settings.CodeLensTest: runTestCodeLens, // commands: Test - settings.CodeLensRegenerateCgo: regenerateCgoLens, // commands: RegenerateCgo - settings.CodeLensGCDetails: toggleDetailsCodeLens, // commands: GCDetails + settings.CodeLensGenerate: goGenerateCodeLens, // commands: Generate + settings.CodeLensTest: runTestCodeLens, // commands: Test + settings.CodeLensRegenerateCgo: regenerateCgoLens, // commands: RegenerateCgo } } @@ -196,21 +195,3 @@ func regenerateCgoLens(ctx context.Context, snapshot *cache.Snapshot, fh file.Ha cmd := command.NewRegenerateCgoCommand("regenerate cgo definitions", command.URIArg{URI: puri}) return []protocol.CodeLens{{Range: rng, Command: cmd}}, nil } - -func toggleDetailsCodeLens(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle) ([]protocol.CodeLens, error) { - pgf, err := snapshot.ParseGo(ctx, fh, parsego.Full) - if err != nil { - return nil, err - } - if !pgf.File.Package.IsValid() { - // Without a package name we have nowhere to put the codelens, so give up. - return nil, nil - } - rng, err := pgf.PosRange(pgf.File.Package, pgf.File.Package) - if err != nil { - return nil, err - } - puri := fh.URI() - cmd := command.NewGCDetailsCommand("Toggle gc annotation details", puri) - return []protocol.CodeLens{{Range: rng, Command: cmd}}, nil -} diff --git a/gopls/internal/golang/codeaction.go b/gopls/internal/golang/codeaction.go index cb0af2301da..6f53a8a3302 100644 --- a/gopls/internal/golang/codeaction.go +++ b/gopls/internal/golang/codeaction.go @@ -231,6 +231,7 @@ var codeActionProducers = [...]codeActionProducer{ {kind: settings.GoDoc, fn: goDoc, needPkg: true}, {kind: settings.GoFreeSymbols, fn: goFreeSymbols}, {kind: settings.GoTest, fn: goTest}, + {kind: settings.GoToggleCompilerOptDetails, fn: toggleCompilerOptDetails}, {kind: settings.GoplsDocFeatures, fn: goplsDocFeatures}, {kind: settings.RefactorExtractFunction, fn: refactorExtractFunction}, {kind: settings.RefactorExtractMethod, fn: refactorExtractMethod}, @@ -871,3 +872,11 @@ func goAssembly(ctx context.Context, req *codeActionsRequest) error { } return nil } + +// toggleCompilerOptDetails produces "Toggle compiler optimization details" code action. +// See [server.commandHandler.ToggleCompilerOptDetails] for command implementation. +func toggleCompilerOptDetails(ctx context.Context, req *codeActionsRequest) error { + cmd := command.NewGCDetailsCommand("Toggle compiler optimization details", req.fh.URI()) + req.addCommandAction(cmd, false) + return nil +} diff --git a/gopls/internal/golang/gc_annotations.go b/gopls/internal/golang/compileropt.go similarity index 72% rename from gopls/internal/golang/gc_annotations.go rename to gopls/internal/golang/compileropt.go index 618216f6306..0ba4e71d2fd 100644 --- a/gopls/internal/golang/gc_annotations.go +++ b/gopls/internal/golang/compileropt.go @@ -20,15 +20,10 @@ import ( "golang.org/x/tools/internal/event" ) -// GCOptimizationDetails invokes the Go compiler on the specified -// package and reports its log of optimizations decisions as a set of -// diagnostics. -// -// TODO(adonovan): this feature needs more consistent and informative naming. -// Now that the compiler is cmd/compile, "GC" now means only "garbage collection". -// I propose "(Toggle|Display) Go compiler optimization details" in the UI, -// and CompilerOptimizationDetails for this function and compileropts.go for the file. -func GCOptimizationDetails(ctx context.Context, snapshot *cache.Snapshot, mp *metadata.Package) (map[protocol.DocumentURI][]*cache.Diagnostic, error) { +// CompilerOptDetails invokes the Go compiler with the "-json=0,dir" +// flag on the specified package, parses its log of optimization +// decisions, and returns them as a set of diagnostics. +func CompilerOptDetails(ctx context.Context, snapshot *cache.Snapshot, mp *metadata.Package) (map[protocol.DocumentURI][]*cache.Diagnostic, error) { if len(mp.CompiledGoFiles) == 0 { return nil, nil } @@ -39,7 +34,7 @@ func GCOptimizationDetails(ctx context.Context, snapshot *cache.Snapshot, mp *me } defer func() { if err := os.RemoveAll(outDir); err != nil { - event.Error(ctx, "cleaning gcdetails dir", err) + event.Error(ctx, "cleaning details dir", err) } }() @@ -51,13 +46,13 @@ func GCOptimizationDetails(ctx context.Context, snapshot *cache.Snapshot, mp *me defer os.Remove(tmpFile.Name()) outDirURI := protocol.URIFromPath(outDir) - // GC details doesn't handle Windows URIs in the form of "file:///C:/...", + // details doesn't handle Windows URIs in the form of "file:///C:/...", // so rewrite them to "file://C:/...". See golang/go#41614. if !strings.HasPrefix(outDir, "/") { outDirURI = protocol.DocumentURI(strings.Replace(string(outDirURI), "file:///", "file://", 1)) } inv, cleanupInvocation, err := snapshot.GoCommandInvocation(cache.NoNetwork, pkgDir, "build", []string{ - fmt.Sprintf("-gcflags=-json=0,%s", outDirURI), + fmt.Sprintf("-gcflags=-json=0,%s", outDirURI), // JSON schema version 0 fmt.Sprintf("-o=%s", tmpFile.Name()), ".", }) @@ -97,6 +92,7 @@ func GCOptimizationDetails(ctx context.Context, snapshot *cache.Snapshot, mp *me return reports, parseError } +// parseDetailsFile parses the file written by the Go compiler which contains a JSON-encoded protocol.Diagnostic. func parseDetailsFile(filename string, options *settings.Options) (protocol.DocumentURI, []*cache.Diagnostic, error) { buf, err := os.ReadFile(filename) if err != nil { @@ -136,13 +132,27 @@ func parseDetailsFile(filename string, options *settings.Options) (protocol.Docu if !showDiagnostic(msg, d.Source, options) { continue } + + // zeroIndexedRange subtracts 1 from the line and + // range, because the compiler output neglects to + // convert from 1-based UTF-8 coordinates to 0-based UTF-16. + // (See GOROOT/src/cmd/compile/internal/logopt/log_opts.go.) + // TODO(rfindley): also translate UTF-8 to UTF-16. + zeroIndexedRange := func(rng protocol.Range) protocol.Range { + return protocol.Range{ + Start: protocol.Position{ + Line: rng.Start.Line - 1, + Character: rng.Start.Character - 1, + }, + End: protocol.Position{ + Line: rng.End.Line - 1, + Character: rng.End.Character - 1, + }, + } + } + var related []protocol.DiagnosticRelatedInformation for _, ri := range d.RelatedInformation { - // TODO(rfindley): The compiler uses LSP-like JSON to encode gc details, - // however the positions it uses are 1-based UTF-8: - // https://github.com/golang/go/blob/master/src/cmd/compile/internal/logopt/log_opts.go - // - // Here, we adjust for 0-based positions, but do not translate UTF-8 to UTF-16. related = append(related, protocol.DiagnosticRelatedInformation{ Location: protocol.Location{ URI: ri.Location.URI, @@ -156,7 +166,7 @@ func parseDetailsFile(filename string, options *settings.Options) (protocol.Docu Range: zeroIndexedRange(d.Range), Message: msg, Severity: d.Severity, - Source: cache.OptimizationDetailsError, // d.Source is always "go compiler" as of 1.16, use our own + Source: cache.CompilerOptDetailsInfo, // d.Source is always "go compiler" as of 1.16, use our own Tags: d.Tags, Related: related, } @@ -175,6 +185,26 @@ func showDiagnostic(msg, source string, o *settings.Options) bool { if o.Annotations == nil { return true } + + // The strings below were gathered by grepping the source of + // cmd/compile for literal arguments in calls to logopt.LogOpt. + // (It is not a well defined set.) + // + // - canInlineFunction + // - cannotInlineCall + // - cannotInlineFunction + // - escape + // - escapes + // - isInBounds + // - isSliceInBounds + // - leak + // - nilcheck + // + // Additional ones not handled by logic below: + // - copy + // - iteration-variable-to-{heap,stack} + // - loop-modified-{range,for} + switch { case strings.HasPrefix(msg, "canInline") || strings.HasPrefix(msg, "cannotInline") || @@ -191,20 +221,6 @@ func showDiagnostic(msg, source string, o *settings.Options) bool { return false } -// The range produced by the compiler is 1-indexed, so subtract range by 1. -func zeroIndexedRange(rng protocol.Range) protocol.Range { - return protocol.Range{ - Start: protocol.Position{ - Line: rng.Start.Line - 1, - Character: rng.Start.Character - 1, - }, - End: protocol.Position{ - Line: rng.End.Line - 1, - Character: rng.End.Character - 1, - }, - } -} - func findJSONFiles(dir string) ([]string, error) { ans := []string{} f := func(path string, fi os.FileInfo, _ error) error { diff --git a/gopls/internal/protocol/command/command_gen.go b/gopls/internal/protocol/command/command_gen.go index 9991c95680e..28a7f44e88f 100644 --- a/gopls/internal/protocol/command/command_gen.go +++ b/gopls/internal/protocol/command/command_gen.go @@ -60,7 +60,6 @@ const ( StopProfile Command = "gopls.stop_profile" Test Command = "gopls.test" Tidy Command = "gopls.tidy" - ToggleGCDetails Command = "gopls.toggle_gc_details" UpdateGoSum Command = "gopls.update_go_sum" UpgradeDependency Command = "gopls.upgrade_dependency" Vendor Command = "gopls.vendor" @@ -106,7 +105,6 @@ var Commands = []Command{ StopProfile, Test, Tidy, - ToggleGCDetails, UpdateGoSum, UpgradeDependency, Vendor, @@ -326,12 +324,6 @@ func Dispatch(ctx context.Context, params *protocol.ExecuteCommandParams, s Inte return nil, err } return nil, s.Tidy(ctx, a0) - case ToggleGCDetails: - var a0 URIArg - if err := UnmarshalArgs(params.Arguments, &a0); err != nil { - return nil, err - } - return nil, s.ToggleGCDetails(ctx, a0) case UpdateGoSum: var a0 URIArgs if err := UnmarshalArgs(params.Arguments, &a0); err != nil { @@ -652,14 +644,6 @@ func NewTidyCommand(title string, a0 URIArgs) *protocol.Command { } } -func NewToggleGCDetailsCommand(title string, a0 URIArg) *protocol.Command { - return &protocol.Command{ - Title: title, - Command: ToggleGCDetails.String(), - Arguments: MustMarshalArgs(a0), - } -} - func NewUpdateGoSumCommand(title string, a0 URIArgs) *protocol.Command { return &protocol.Command{ Title: title, diff --git a/gopls/internal/protocol/command/commandmeta/meta.go b/gopls/internal/protocol/command/commandmeta/meta.go index 166fae61e04..f147898e192 100644 --- a/gopls/internal/protocol/command/commandmeta/meta.go +++ b/gopls/internal/protocol/command/commandmeta/meta.go @@ -219,8 +219,8 @@ func lspName(methodName string) string { // // For example: // -// "RunTests" -> []string{"Run", "Tests"} -// "GCDetails" -> []string{"GC", "Details"} +// "RunTests" -> []string{"Run", "Tests"} +// "ClientOpenURL" -> []string{"Client", "Open", "URL"} func splitCamel(s string) []string { var words []string for len(s) > 0 { diff --git a/gopls/internal/protocol/command/interface.go b/gopls/internal/protocol/command/interface.go index 3bef5d0df6f..b0e80a4129e 100644 --- a/gopls/internal/protocol/command/interface.go +++ b/gopls/internal/protocol/command/interface.go @@ -132,17 +132,15 @@ type Interface interface { // Runs `go get` to fetch a package. GoGetPackage(context.Context, GoGetPackageArgs) error - // GCDetails: Toggle gc_details + // GCDetails: Toggle display of compiler optimization details // - // Toggle the calculation of gc annotations. - GCDetails(context.Context, protocol.DocumentURI) error - - // TODO: deprecate GCDetails in favor of ToggleGCDetails below. - - // ToggleGCDetails: Toggle gc_details + // Toggle the per-package flag that causes Go compiler + // optimization decisions to be reported as diagnostics. // - // Toggle the calculation of gc annotations. - ToggleGCDetails(context.Context, URIArg) error + // (The name is a legacy of a time when the Go compiler was + // known as "gc". Renaming the command would break custom + // client-side logic in VS Code.) + GCDetails(context.Context, protocol.DocumentURI) error // ListKnownPackages: List known packages // diff --git a/gopls/internal/server/code_action.go b/gopls/internal/server/code_action.go index 9c01e65780d..c36e7c33f94 100644 --- a/gopls/internal/server/code_action.go +++ b/gopls/internal/server/code_action.go @@ -192,7 +192,8 @@ func (s *server) CodeAction(ctx context.Context, params *protocol.CodeActionPara settings.GoDoc, settings.GoFreeSymbols, settings.GoAssembly, - settings.GoplsDocFeatures: + settings.GoplsDocFeatures, + settings.GoToggleCompilerOptDetails: return false // read-only query } return true // potential write operation diff --git a/gopls/internal/server/command.go b/gopls/internal/server/command.go index 9995d02117e..e785625655e 100644 --- a/gopls/internal/server/command.go +++ b/gopls/internal/server/command.go @@ -1025,23 +1025,19 @@ func (s *server) getUpgrades(ctx context.Context, snapshot *cache.Snapshot, uri } func (c *commandHandler) GCDetails(ctx context.Context, uri protocol.DocumentURI) error { - return c.ToggleGCDetails(ctx, command.URIArg{URI: uri}) -} - -func (c *commandHandler) ToggleGCDetails(ctx context.Context, args command.URIArg) error { return c.run(ctx, commandConfig{ - progress: "Toggling GC Details", - forURI: args.URI, + progress: "Toggling display of compiler optimization details", + forURI: uri, }, func(ctx context.Context, deps commandDeps) error { - return c.modifyState(ctx, FromToggleGCDetails, func() (*cache.Snapshot, func(), error) { + return c.modifyState(ctx, FromToggleCompilerOptDetails, func() (*cache.Snapshot, func(), error) { meta, err := golang.NarrowestMetadataForFile(ctx, deps.snapshot, deps.fh.URI()) if err != nil { return nil, nil, err } - wantDetails := !deps.snapshot.WantGCDetails(meta.ID) // toggle the gc details state + want := !deps.snapshot.WantCompilerOptDetails(meta.ID) // toggle per-package flag return c.s.session.InvalidateView(ctx, deps.snapshot.View(), cache.StateChange{ - GCDetails: map[metadata.PackageID]bool{ - meta.ID: wantDetails, + CompilerOptDetails: map[metadata.PackageID]bool{ + meta.ID: want, }, }) }) diff --git a/gopls/internal/server/diagnostics.go b/gopls/internal/server/diagnostics.go index d22daab241e..b5699c84de4 100644 --- a/gopls/internal/server/diagnostics.go +++ b/gopls/internal/server/diagnostics.go @@ -484,8 +484,8 @@ func (s *server) diagnose(ctx context.Context, snapshot *cache.Snapshot) (diagMa wg.Add(1) go func() { defer wg.Done() - gcDetailsReports, err := s.gcDetailsDiagnostics(ctx, snapshot, toDiagnose) - store("collecting gc_details", gcDetailsReports, err) + compilerOptDetailsDiags, err := s.compilerOptDetailsDiagnostics(ctx, snapshot, toDiagnose) + store("collecting compiler optimization details", compilerOptDetailsDiags, err) }() // Package diagnostics and analysis diagnostics must both be computed and @@ -536,30 +536,30 @@ func (s *server) diagnose(ctx context.Context, snapshot *cache.Snapshot) (diagMa return diagnostics, nil } -func (s *server) gcDetailsDiagnostics(ctx context.Context, snapshot *cache.Snapshot, toDiagnose map[metadata.PackageID]*metadata.Package) (diagMap, error) { - // Process requested gc_details diagnostics. +func (s *server) compilerOptDetailsDiagnostics(ctx context.Context, snapshot *cache.Snapshot, toDiagnose map[metadata.PackageID]*metadata.Package) (diagMap, error) { + // Process requested diagnostics about compiler optimization details. // // TODO(rfindley): This should memoize its results if the package has not changed. // Consider that these points, in combination with the note below about - // races, suggest that gc_details should be tracked on the Snapshot. - var toGCDetail map[metadata.PackageID]*metadata.Package + // races, suggest that compiler optimization details should be tracked on the Snapshot. + var detailPkgs map[metadata.PackageID]*metadata.Package for _, mp := range toDiagnose { - if snapshot.WantGCDetails(mp.ID) { - if toGCDetail == nil { - toGCDetail = make(map[metadata.PackageID]*metadata.Package) + if snapshot.WantCompilerOptDetails(mp.ID) { + if detailPkgs == nil { + detailPkgs = make(map[metadata.PackageID]*metadata.Package) } - toGCDetail[mp.ID] = mp + detailPkgs[mp.ID] = mp } } diagnostics := make(diagMap) - for _, mp := range toGCDetail { - gcReports, err := golang.GCOptimizationDetails(ctx, snapshot, mp) + for _, mp := range detailPkgs { + perFileDiags, err := golang.CompilerOptDetails(ctx, snapshot, mp) if err != nil { - event.Error(ctx, "warning: gc details", err, append(snapshot.Labels(), label.Package.Of(string(mp.ID)))...) + event.Error(ctx, "warning: compiler optimization details", err, append(snapshot.Labels(), label.Package.Of(string(mp.ID)))...) continue } - for uri, diags := range gcReports { + for uri, diags := range perFileDiags { diagnostics[uri] = append(diagnostics[uri], diags...) } } diff --git a/gopls/internal/server/text_synchronization.go b/gopls/internal/server/text_synchronization.go index 6aef24691d6..ad1266d783e 100644 --- a/gopls/internal/server/text_synchronization.go +++ b/gopls/internal/server/text_synchronization.go @@ -61,9 +61,9 @@ const ( // ResetGoModDiagnostics command. FromResetGoModDiagnostics - // FromToggleGCDetails refers to state changes resulting from toggling - // gc_details on or off for a package. - FromToggleGCDetails + // FromToggleCompilerOptDetails refers to state changes resulting from toggling + // a package's compiler optimization details flag. + FromToggleCompilerOptDetails ) func (m ModificationSource) String() string { diff --git a/gopls/internal/settings/codeactionkind.go b/gopls/internal/settings/codeactionkind.go index 0daf3cb5999..fcce7cd2682 100644 --- a/gopls/internal/settings/codeactionkind.go +++ b/gopls/internal/settings/codeactionkind.go @@ -75,11 +75,12 @@ import "golang.org/x/tools/gopls/internal/protocol" // is not VS Code's default behavior; see editor.codeActionsOnSave.) const ( // source - GoAssembly protocol.CodeActionKind = "source.assembly" - GoDoc protocol.CodeActionKind = "source.doc" - GoFreeSymbols protocol.CodeActionKind = "source.freesymbols" - GoTest protocol.CodeActionKind = "source.test" - AddTest protocol.CodeActionKind = "source.addTest" + GoAssembly protocol.CodeActionKind = "source.assembly" + GoDoc protocol.CodeActionKind = "source.doc" + GoFreeSymbols protocol.CodeActionKind = "source.freesymbols" + GoTest protocol.CodeActionKind = "source.test" + GoToggleCompilerOptDetails protocol.CodeActionKind = "source.toggleCompilerOptDetails" + AddTest protocol.CodeActionKind = "source.addTest" // gopls GoplsDocFeatures protocol.CodeActionKind = "gopls.doc.features" diff --git a/gopls/internal/settings/default.go b/gopls/internal/settings/default.go index 3048f90bd3b..270fc56103d 100644 --- a/gopls/internal/settings/default.go +++ b/gopls/internal/settings/default.go @@ -122,7 +122,6 @@ func DefaultOptions(overrides ...func(*Options)) *Options { CodeLensGenerate: true, CodeLensRegenerateCgo: true, CodeLensTidy: true, - CodeLensGCDetails: false, CodeLensUpgradeDependency: true, CodeLensVendor: true, CodeLensRunGovulncheck: false, // TODO(hyangah): enable diff --git a/gopls/internal/settings/settings.go b/gopls/internal/settings/settings.go index 4dd85a59182..468c26c5ec0 100644 --- a/gopls/internal/settings/settings.go +++ b/gopls/internal/settings/settings.go @@ -16,6 +16,10 @@ import ( "golang.org/x/tools/gopls/internal/util/frob" ) +// An Annotation is a category of Go compiler optimization diagnostic. +// +// TODO(adonovan): this seems like a large control surface. Let's +// remove it, and just show all kinds when the flag is enabled. type Annotation string const ( @@ -175,7 +179,6 @@ type UIOptions struct { // ... // "codelenses": { // "generate": false, // Don't show the `go generate` lens. - // "gc_details": true // Show a code lens toggling the display of gc's choices. // } // ... // } @@ -209,25 +212,6 @@ type CodeLensSource string // matches the name of one of the command.Commands returned by it, // but that isn't essential.) const ( - // Toggle display of Go compiler optimization decisions - // - // This codelens source causes the `package` declaration of - // each file to be annotated with a command to toggle the - // state of the per-session variable that controls whether - // optimization decisions from the Go compiler (formerly known - // as "gc") should be displayed as diagnostics. - // - // Optimization decisions include: - // - whether a variable escapes, and how escape is inferred; - // - whether a nil-pointer check is implied or eliminated; - // - whether a function can be inlined. - // - // TODO(adonovan): this source is off by default because the - // annotation is annoying and because VS Code has a separate - // "Toggle gc details" command. Replace it with a Code Action - // ("Source action..."). - CodeLensGCDetails CodeLensSource = "gc_details" - // Run `go generate` // // This codelens source annotates any `//go:generate` comments @@ -442,8 +426,10 @@ type DiagnosticOptions struct { // [Staticcheck's website](https://staticcheck.io/docs/checks/). Staticcheck bool `status:"experimental"` - // Annotations specifies the various kinds of optimization diagnostics - // that should be reported by the gc_details command. + // Annotations specifies the various kinds of compiler + // optimization details that should be reported as diagnostics + // when enabled for a package by the "Toggle compiler + // optimization details" (`gopls.gc_details`) command. Annotations map[Annotation]bool `status:"experimental"` // Vulncheck enables vulnerability scanning. diff --git a/gopls/internal/test/integration/codelens/gcdetails_test.go b/gopls/internal/test/integration/codelens/gcdetails_test.go deleted file mode 100644 index 67750382de0..00000000000 --- a/gopls/internal/test/integration/codelens/gcdetails_test.go +++ /dev/null @@ -1,121 +0,0 @@ -// Copyright 2020 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package codelens - -import ( - "runtime" - "testing" - - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/protocol/command" - "golang.org/x/tools/gopls/internal/server" - . "golang.org/x/tools/gopls/internal/test/integration" - "golang.org/x/tools/gopls/internal/test/integration/fake" - "golang.org/x/tools/gopls/internal/util/bug" -) - -func TestGCDetails_Toggle(t *testing.T) { - if runtime.GOOS == "android" { - t.Skipf("the gc details code lens doesn't work on Android") - } - - const mod = ` --- go.mod -- -module mod.com - -go 1.15 --- main.go -- -package main - -import "fmt" - -func main() { - fmt.Println(42) -} -` - WithOptions( - Settings{ - "codelenses": map[string]bool{ - "gc_details": true, - }, - }, - ).Run(t, mod, func(t *testing.T, env *Env) { - env.OpenFile("main.go") - env.ExecuteCodeLensCommand("main.go", command.GCDetails, nil) - - env.OnceMet( - CompletedWork(server.DiagnosticWorkTitle(server.FromToggleGCDetails), 1, true), - Diagnostics( - ForFile("main.go"), - WithMessage("42 escapes"), - WithSeverityTags("optimizer details", protocol.SeverityInformation, nil), - ), - ) - - // GCDetails diagnostics should be reported even on unsaved - // edited buffers, thanks to the magic of overlays. - env.SetBufferContent("main.go", ` -package main -func main() {} -func f(x int) *int { return &x }`) - env.AfterChange(Diagnostics( - ForFile("main.go"), - WithMessage("x escapes"), - WithSeverityTags("optimizer details", protocol.SeverityInformation, nil), - )) - - // Toggle the GC details code lens again so now it should be off. - env.ExecuteCodeLensCommand("main.go", command.GCDetails, nil) - env.OnceMet( - CompletedWork(server.DiagnosticWorkTitle(server.FromToggleGCDetails), 2, true), - NoDiagnostics(ForFile("main.go")), - ) - }) -} - -// Test for the crasher in golang/go#54199 -func TestGCDetails_NewFile(t *testing.T) { - bug.PanicOnBugs = false - const src = ` --- go.mod -- -module mod.test - -go 1.12 -` - - WithOptions( - Settings{ - "codelenses": map[string]bool{ - "gc_details": true, - }, - }, - ).Run(t, src, func(t *testing.T, env *Env) { - env.CreateBuffer("p_test.go", "") - - hasGCDetails := func() bool { - lenses := env.CodeLens("p_test.go") // should not crash - for _, lens := range lenses { - if lens.Command.Command == command.GCDetails.String() { - return true - } - } - return false - } - - // With an empty file, we shouldn't get the gc_details codelens because - // there is nowhere to position it (it needs a package name). - if hasGCDetails() { - t.Errorf("got the gc_details codelens for an empty file") - } - - // Edit to provide a package name. - env.EditBuffer("p_test.go", fake.NewEdit(0, 0, 0, 0, "package p")) - - // Now we should get the gc_details codelens. - if !hasGCDetails() { - t.Errorf("didn't get the gc_details codelens for a valid non-empty Go file") - } - }) -} diff --git a/gopls/internal/test/integration/misc/codeactions_test.go b/gopls/internal/test/integration/misc/codeactions_test.go index a9d0ce8b149..c62a3898e9b 100644 --- a/gopls/internal/test/integration/misc/codeactions_test.go +++ b/gopls/internal/test/integration/misc/codeactions_test.go @@ -68,12 +68,14 @@ func g() {} settings.GoAssembly, settings.GoDoc, settings.GoFreeSymbols, + settings.GoToggleCompilerOptDetails, settings.GoplsDocFeatures, settings.RefactorInlineCall) check("gen/a.go", settings.GoAssembly, settings.GoDoc, settings.GoFreeSymbols, + settings.GoToggleCompilerOptDetails, settings.GoplsDocFeatures) }) } diff --git a/gopls/internal/test/integration/misc/compileropt_test.go b/gopls/internal/test/integration/misc/compileropt_test.go new file mode 100644 index 00000000000..e066306e229 --- /dev/null +++ b/gopls/internal/test/integration/misc/compileropt_test.go @@ -0,0 +1,81 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package misc + +import ( + "runtime" + "testing" + + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/gopls/internal/server" + "golang.org/x/tools/gopls/internal/settings" + . "golang.org/x/tools/gopls/internal/test/integration" +) + +// TestCompilerOptDetails exercises the "Toggle compiler optimization details" code action. +func TestCompilerOptDetails(t *testing.T) { + if runtime.GOOS == "android" { + t.Skipf("the compiler optimization details code action doesn't work on Android") + } + + const mod = ` +-- go.mod -- +module mod.com + +go 1.15 +-- main.go -- +package main + +import "fmt" + +func main() { + fmt.Println(42) +} +` + Run(t, mod, func(t *testing.T, env *Env) { + env.OpenFile("main.go") + actions := env.CodeActionForFile("main.go", nil) + + // Execute the "Toggle compiler optimization details" command. + docAction, err := codeActionByKind(actions, settings.GoToggleCompilerOptDetails) + if err != nil { + t.Fatal(err) + } + params := &protocol.ExecuteCommandParams{ + Command: docAction.Command.Command, + Arguments: docAction.Command.Arguments, + } + env.ExecuteCommand(params, nil) + + env.OnceMet( + CompletedWork(server.DiagnosticWorkTitle(server.FromToggleCompilerOptDetails), 1, true), + Diagnostics( + ForFile("main.go"), + AtPosition("main.go", 5, 13), // (LSP coordinates) + WithMessage("42 escapes"), + WithSeverityTags("optimizer details", protocol.SeverityInformation, nil), + ), + ) + + // Diagnostics should be reported even on unsaved + // edited buffers, thanks to the magic of overlays. + env.SetBufferContent("main.go", ` +package main +func main() {} +func f(x int) *int { return &x }`) + env.AfterChange(Diagnostics( + ForFile("main.go"), + WithMessage("x escapes"), + WithSeverityTags("optimizer details", protocol.SeverityInformation, nil), + )) + + // Toggle the flag again so now it should be off. + env.ExecuteCommand(params, nil) + env.OnceMet( + CompletedWork(server.DiagnosticWorkTitle(server.FromToggleCompilerOptDetails), 2, true), + NoDiagnostics(ForFile("main.go")), + ) + }) +} From 5fe60fde9dfbb00cb5a3c43be024f4ff37bf7eec Mon Sep 17 00:00:00 2001 From: Alan Donovan Date: Thu, 2 Jan 2025 21:15:24 -0500 Subject: [PATCH 66/73] internal/settings: drop "annotations" setting This obscure suboption of the already obscure Toggle GC Details feature seems unnecessary. We should just show all the details. Change-Id: I9deff8f317c7a97fe01af1547bf022a506c37f80 Reviewed-on: https://go-review.googlesource.com/c/tools/+/639835 Reviewed-by: Robert Findley Auto-Submit: Alan Donovan LUCI-TryBot-Result: Go LUCI --- gopls/doc/settings.md | 19 ------ gopls/internal/doc/api.json | 34 ----------- gopls/internal/golang/compileropt.go | 73 +++++++----------------- gopls/internal/settings/default.go | 6 -- gopls/internal/settings/settings.go | 70 +---------------------- gopls/internal/settings/settings_test.go | 11 ---- 6 files changed, 22 insertions(+), 191 deletions(-) diff --git a/gopls/doc/settings.md b/gopls/doc/settings.md index d1be214e933..1350e8f7840 100644 --- a/gopls/doc/settings.md +++ b/gopls/doc/settings.md @@ -315,25 +315,6 @@ These analyses are documented on Default: `false`. - -### `annotations map[enum]bool` - -**This setting is experimental and may be deleted.** - -annotations specifies the various kinds of compiler -optimization details that should be reported as diagnostics -when enabled for a package by the "Toggle compiler -optimization details" (`gopls.gc_details`) command. - -Each enum must be one of: - -* `"bounds"` controls bounds checking diagnostics. -* `"escape"` controls diagnostics about escape choices. -* `"inline"` controls diagnostics about inlining choices. -* `"nil"` controls nil checks. - -Default: `{"bounds":true,"escape":true,"inline":true,"nil":true}`. - ### `vulncheck enum` diff --git a/gopls/internal/doc/api.json b/gopls/internal/doc/api.json index 77afb52f8b9..acf79766401 100644 --- a/gopls/internal/doc/api.json +++ b/gopls/internal/doc/api.json @@ -635,40 +635,6 @@ "Status": "experimental", "Hierarchy": "ui.diagnostic" }, - { - "Name": "annotations", - "Type": "map[enum]bool", - "Doc": "annotations specifies the various kinds of compiler\noptimization details that should be reported as diagnostics\nwhen enabled for a package by the \"Toggle compiler\noptimization details\" (`gopls.gc_details`) command.\n", - "EnumKeys": { - "ValueType": "bool", - "Keys": [ - { - "Name": "\"bounds\"", - "Doc": "`\"bounds\"` controls bounds checking diagnostics.\n", - "Default": "true" - }, - { - "Name": "\"escape\"", - "Doc": "`\"escape\"` controls diagnostics about escape choices.\n", - "Default": "true" - }, - { - "Name": "\"inline\"", - "Doc": "`\"inline\"` controls diagnostics about inlining choices.\n", - "Default": "true" - }, - { - "Name": "\"nil\"", - "Doc": "`\"nil\"` controls nil checks.\n", - "Default": "true" - } - ] - }, - "EnumValues": null, - "Default": "{\"bounds\":true,\"escape\":true,\"inline\":true,\"nil\":true}", - "Status": "experimental", - "Hierarchy": "ui.diagnostic" - }, { "Name": "vulncheck", "Type": "enum", diff --git a/gopls/internal/golang/compileropt.go b/gopls/internal/golang/compileropt.go index 0ba4e71d2fd..2a39a5b5ee1 100644 --- a/gopls/internal/golang/compileropt.go +++ b/gopls/internal/golang/compileropt.go @@ -16,7 +16,6 @@ import ( "golang.org/x/tools/gopls/internal/cache" "golang.org/x/tools/gopls/internal/cache/metadata" "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/settings" "golang.org/x/tools/internal/event" ) @@ -69,10 +68,9 @@ func CompilerOptDetails(ctx context.Context, snapshot *cache.Snapshot, mp *metad return nil, err } reports := make(map[protocol.DocumentURI][]*cache.Diagnostic) - opts := snapshot.Options() var parseError error for _, fn := range files { - uri, diagnostics, err := parseDetailsFile(fn, opts) + uri, diagnostics, err := parseDetailsFile(fn) if err != nil { // expect errors for all the files, save 1 parseError = err @@ -93,7 +91,7 @@ func CompilerOptDetails(ctx context.Context, snapshot *cache.Snapshot, mp *metad } // parseDetailsFile parses the file written by the Go compiler which contains a JSON-encoded protocol.Diagnostic. -func parseDetailsFile(filename string, options *settings.Options) (protocol.DocumentURI, []*cache.Diagnostic, error) { +func parseDetailsFile(filename string) (protocol.DocumentURI, []*cache.Diagnostic, error) { buf, err := os.ReadFile(filename) if err != nil { return "", nil, err @@ -124,14 +122,30 @@ func parseDetailsFile(filename string, options *settings.Options) (protocol.Docu if err := dec.Decode(d); err != nil { return "", nil, err } + if d.Source != "go compiler" { + continue + } d.Tags = []protocol.DiagnosticTag{} // must be an actual slice msg := d.Code.(string) if msg != "" { + // Typical message prefixes gathered by grepping the source of + // cmd/compile for literal arguments in calls to logopt.LogOpt. + // (It is not a well defined set.) + // + // - canInlineFunction + // - cannotInlineCall + // - cannotInlineFunction + // - copy + // - escape + // - escapes + // - isInBounds + // - isSliceInBounds + // - iteration-variable-to-{heap,stack} + // - leak + // - loop-modified-{range,for} + // - nilcheck msg = fmt.Sprintf("%s(%s)", msg, d.Message) } - if !showDiagnostic(msg, d.Source, options) { - continue - } // zeroIndexedRange subtracts 1 from the line and // range, because the compiler output neglects to @@ -176,51 +190,6 @@ func parseDetailsFile(filename string, options *settings.Options) (protocol.Docu return uri, diagnostics, nil } -// showDiagnostic reports whether a given diagnostic should be shown to the end -// user, given the current options. -func showDiagnostic(msg, source string, o *settings.Options) bool { - if source != "go compiler" { - return false - } - if o.Annotations == nil { - return true - } - - // The strings below were gathered by grepping the source of - // cmd/compile for literal arguments in calls to logopt.LogOpt. - // (It is not a well defined set.) - // - // - canInlineFunction - // - cannotInlineCall - // - cannotInlineFunction - // - escape - // - escapes - // - isInBounds - // - isSliceInBounds - // - leak - // - nilcheck - // - // Additional ones not handled by logic below: - // - copy - // - iteration-variable-to-{heap,stack} - // - loop-modified-{range,for} - - switch { - case strings.HasPrefix(msg, "canInline") || - strings.HasPrefix(msg, "cannotInline") || - strings.HasPrefix(msg, "inlineCall"): - return o.Annotations[settings.Inline] - case strings.HasPrefix(msg, "escape") || msg == "leak": - return o.Annotations[settings.Escape] - case strings.HasPrefix(msg, "nilcheck"): - return o.Annotations[settings.Nil] - case strings.HasPrefix(msg, "isInBounds") || - strings.HasPrefix(msg, "isSliceInBounds"): - return o.Annotations[settings.Bounds] - } - return false -} - func findJSONFiles(dir string) ([]string, error) { ans := []string{} f := func(path string, fi os.FileInfo, _ error) error { diff --git a/gopls/internal/settings/default.go b/gopls/internal/settings/default.go index 270fc56103d..f9b947b31a8 100644 --- a/gopls/internal/settings/default.go +++ b/gopls/internal/settings/default.go @@ -89,12 +89,6 @@ func DefaultOptions(overrides ...func(*Options)) *Options { }, UIOptions: UIOptions{ DiagnosticOptions: DiagnosticOptions{ - Annotations: map[Annotation]bool{ - Bounds: true, - Escape: true, - Inline: true, - Nil: true, - }, Vulncheck: ModeVulncheckOff, DiagnosticsDelay: 1 * time.Second, DiagnosticsTrigger: DiagnosticsOnEdit, diff --git a/gopls/internal/settings/settings.go b/gopls/internal/settings/settings.go index 468c26c5ec0..785ebd8b582 100644 --- a/gopls/internal/settings/settings.go +++ b/gopls/internal/settings/settings.go @@ -16,26 +16,6 @@ import ( "golang.org/x/tools/gopls/internal/util/frob" ) -// An Annotation is a category of Go compiler optimization diagnostic. -// -// TODO(adonovan): this seems like a large control surface. Let's -// remove it, and just show all kinds when the flag is enabled. -type Annotation string - -const ( - // Nil controls nil checks. - Nil Annotation = "nil" - - // Escape controls diagnostics about escape choices. - Escape Annotation = "escape" - - // Inline controls diagnostics about inlining choices. - Inline Annotation = "inline" - - // Bounds controls bounds checking diagnostics. - Bounds Annotation = "bounds" -) - // Options holds various configuration that affects Gopls execution, organized // by the nature or origin of the settings. // @@ -426,12 +406,6 @@ type DiagnosticOptions struct { // [Staticcheck's website](https://staticcheck.io/docs/checks/). Staticcheck bool `status:"experimental"` - // Annotations specifies the various kinds of compiler - // optimization details that should be reported as diagnostics - // when enabled for a package by the "Toggle compiler - // optimization details" (`gopls.gc_details`) command. - Annotations map[Annotation]bool `status:"experimental"` - // Vulncheck enables vulnerability scanning. Vulncheck VulncheckMode `status:"experimental"` @@ -1043,7 +1017,7 @@ func (o *Options) setOne(name string, value any) error { return setBoolMap(&o.Hints, value) case "annotations": - return setAnnotationMap(&o.Annotations, value) + return deprecatedError("the 'annotations' setting was removed in gopls/v0.18.0; all compiler optimization details are now shown") case "vulncheck": return setEnum(&o.Vulncheck, value, @@ -1299,48 +1273,6 @@ func setDuration(dest *time.Duration, value any) error { return nil } -func setAnnotationMap(dest *map[Annotation]bool, value any) error { - all, err := asBoolMap[string](value) - if err != nil { - return err - } - if all == nil { - return nil - } - // Default to everything enabled by default. - m := make(map[Annotation]bool) - for k, enabled := range all { - var a Annotation - if err := setEnum(&a, k, - Nil, - Escape, - Inline, - Bounds); err != nil { - // In case of an error, process any legacy values. - switch k { - case "noEscape": - m[Escape] = false - return fmt.Errorf(`"noEscape" is deprecated, set "Escape: false" instead`) - case "noNilcheck": - m[Nil] = false - return fmt.Errorf(`"noNilcheck" is deprecated, set "Nil: false" instead`) - - case "noInline": - m[Inline] = false - return fmt.Errorf(`"noInline" is deprecated, set "Inline: false" instead`) - case "noBounds": - m[Bounds] = false - return fmt.Errorf(`"noBounds" is deprecated, set "Bounds: false" instead`) - default: - return err - } - } - m[a] = enabled - } - *dest = m - return nil -} - func setBoolMap[K ~string](dest *map[K]bool, value any) error { m, err := asBoolMap[K](value) if err != nil { diff --git a/gopls/internal/settings/settings_test.go b/gopls/internal/settings/settings_test.go index 345ec81574f..63b4aded8bd 100644 --- a/gopls/internal/settings/settings_test.go +++ b/gopls/internal/settings/settings_test.go @@ -180,17 +180,6 @@ func TestOptions_Set(t *testing.T) { return len(o.DirectoryFilters) == 0 }, }, - { - name: "annotations", - value: map[string]any{ - "Nil": false, - "noBounds": true, - }, - wantError: true, - check: func(o Options) bool { - return !o.Annotations[Nil] && !o.Annotations[Bounds] - }, - }, { name: "vulncheck", value: []any{"invalid"}, From 192ac777df0f45b4fdbd9c091bbb4c21b465b2da Mon Sep 17 00:00:00 2001 From: Alan Donovan Date: Thu, 2 Jan 2025 21:05:32 -0500 Subject: [PATCH 67/73] internal/golang: CodeAction: don't return empty source.doc titles The source.doc code action was observed to produce empty command titles on occasion; it should not offer a command in that case. (A test doesn't make much sense because it would assert that a particular input doesn't produce a command, whereas the invariant that matters is that empty command titles are never shown.) Change-Id: Ibe5de418954d299ea0d7809c35e7496ff353be6f Reviewed-on: https://go-review.googlesource.com/c/tools/+/639836 Auto-Submit: Alan Donovan Reviewed-by: Robert Findley Commit-Queue: Alan Donovan LUCI-TryBot-Result: Go LUCI --- gopls/internal/golang/codeaction.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/gopls/internal/golang/codeaction.go b/gopls/internal/golang/codeaction.go index 6f53a8a3302..627ba1a60d6 100644 --- a/gopls/internal/golang/codeaction.go +++ b/gopls/internal/golang/codeaction.go @@ -442,8 +442,10 @@ func goplsDocFeatures(ctx context.Context, req *codeActionsRequest) error { // See [server.commandHandler.Doc] for command implementation. func goDoc(ctx context.Context, req *codeActionsRequest) error { _, _, title := DocFragment(req.pkg, req.pgf, req.start, req.end) - cmd := command.NewDocCommand(title, command.DocArgs{Location: req.loc, ShowDocument: true}) - req.addCommandAction(cmd, false) + if title != "" { + cmd := command.NewDocCommand(title, command.DocArgs{Location: req.loc, ShowDocument: true}) + req.addCommandAction(cmd, false) + } return nil } From 98a190b4a83e5eac14e91b837f868ed73fb4b383 Mon Sep 17 00:00:00 2001 From: Alan Donovan Date: Thu, 2 Jan 2025 12:37:42 -0500 Subject: [PATCH 68/73] gopls/internal/analysis/unusedfunc: analyzer for unused funcs/methods This CL defines a new gopls analyzer that reports unused functions and methods using a local heuristic suitable for the analysis framework, delivering some of the value of cmd/deadcode but with the value of near real-time feedback and gopls integration. Like unusedparams, it assumes that it is running within gopls' analysis driver, which always chooses the "widest" package for a given file. Without this assumption, the additional files for an in-package test may invalidate the analyzer's findings. Unfortunately a rather large number of marker tests define throwaway functions called f that not trigger a diagnostic. They have been updated to finesse the problem. + test, doc, relnote Change-Id: I85ef593eee7a6940779ee27a2455d9090a3e8c7c Reviewed-on: https://go-review.googlesource.com/c/tools/+/639716 Reviewed-by: Robert Findley Auto-Submit: Alan Donovan LUCI-TryBot-Result: Go LUCI --- gopls/doc/analyzers.md | 31 +++ gopls/doc/release/v0.18.0.md | 10 +- gopls/internal/analysis/unusedfunc/doc.go | 34 ++++ gopls/internal/analysis/unusedfunc/main.go | 15 ++ .../analysis/unusedfunc/testdata/src/a/a.go | 41 ++++ .../unusedfunc/testdata/src/a/a.go.golden | 26 +++ .../analysis/unusedfunc/unusedfunc.go | 183 ++++++++++++++++++ .../analysis/unusedfunc/unusedfunc_test.go | 17 ++ gopls/internal/doc/api.json | 11 ++ gopls/internal/golang/change_signature.go | 4 - gopls/internal/server/diagnostics.go | 2 +- gopls/internal/settings/analysis.go | 2 + .../diagnostics/diagnostics_test.go | 2 +- .../diagnostics/gopackagesdriver_test.go | 3 - .../test/integration/misc/compileropt_test.go | 2 +- .../test/integration/misc/imports_test.go | 4 +- .../testdata/callhierarchy/issue64451.txt | 8 +- .../codeaction/extract_variable-toplevel.txt | 6 +- .../codeaction/functionextraction.txt | 4 + .../functionextraction_issue50851.txt | 4 +- .../marker/testdata/codeaction/grouplines.txt | 20 +- .../testdata/codeaction/inline_issue67336.txt | 4 +- .../testdata/codeaction/inline_issue68554.txt | 4 +- .../testdata/codeaction/removeparam.txt | 4 + .../codeaction/removeparam_resolve.txt | 4 + .../marker/testdata/codeaction/splitlines.txt | 24 +-- .../test/marker/testdata/codelens/test.txt | 6 + .../marker/testdata/definition/branch.txt | 28 +-- .../test/marker/testdata/foldingrange/a.txt | 8 +- .../testdata/foldingrange/a_lineonly.txt | 8 +- .../test/marker/testdata/foldingrange/bad.txt | 8 +- .../testdata/highlight/highlight_kind.txt | 2 +- .../test/marker/testdata/hover/comment.txt | 4 +- .../test/marker/testdata/hover/embed.txt | 2 + .../test/marker/testdata/hover/linkable.txt | 2 + .../test/marker/testdata/hover/linkname.txt | 2 + .../test/marker/testdata/hover/methods.txt | 2 + .../test/marker/testdata/hover/return.txt | 4 +- .../testdata/quickfix/embeddirective.txt | 2 +- .../testdata/quickfix/self_assignment.txt | 2 +- .../test/marker/testdata/rename/basic.txt | 6 +- .../test/marker/testdata/rename/conflict.txt | 6 +- .../test/marker/testdata/rename/crosspkg.txt | 2 + .../test/marker/testdata/rename/generics.txt | 5 + .../test/marker/testdata/rename/prepare.txt | 4 + .../test/marker/testdata/symbol/basic.txt | 2 + .../workspacesymbol/casesensitive.txt | 2 + .../testdata/workspacesymbol/issue44806.txt | 24 +-- 48 files changed, 501 insertions(+), 99 deletions(-) create mode 100644 gopls/internal/analysis/unusedfunc/doc.go create mode 100644 gopls/internal/analysis/unusedfunc/main.go create mode 100644 gopls/internal/analysis/unusedfunc/testdata/src/a/a.go create mode 100644 gopls/internal/analysis/unusedfunc/testdata/src/a/a.go.golden create mode 100644 gopls/internal/analysis/unusedfunc/unusedfunc.go create mode 100644 gopls/internal/analysis/unusedfunc/unusedfunc_test.go diff --git a/gopls/doc/analyzers.md b/gopls/doc/analyzers.md index 9811393611e..2905a0e5336 100644 --- a/gopls/doc/analyzers.md +++ b/gopls/doc/analyzers.md @@ -903,6 +903,37 @@ Default: on. Package documentation: [unsafeptr](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/unsafeptr) + +## `unusedfunc`: check for unused functions and methods + + +The unusedfunc analyzer reports functions and methods that are +never referenced outside of their own declaration. + +A function is considered unused if it is unexported and not +referenced (except within its own declaration). + +A method is considered unused if it is unexported, not referenced +(except within its own declaration), and its name does not match +that of any method of an interface type declared within the same +package. + +The tool may report a false positive for a declaration of an +unexported function that is referenced from another package using +the go:linkname mechanism, if the declaration's doc comment does +not also have a go:linkname comment. (Such code is in any case +strongly discouraged: linkname annotations, if they must be used at +all, should be used on both the declaration and the alias.) + +The unusedfunc algorithm is not as precise as the +golang.org/x/tools/cmd/deadcode tool, but it has the advantage that +it runs within the modular analysis framework, enabling near +real-time feedback within gopls. + +Default: on. + +Package documentation: [unusedfunc](https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/unusedfunc) + ## `unusedparams`: check for unused parameters of functions diff --git a/gopls/doc/release/v0.18.0.md b/gopls/doc/release/v0.18.0.md index 2337b99e657..9f7ddd0909b 100644 --- a/gopls/doc/release/v0.18.0.md +++ b/gopls/doc/release/v0.18.0.md @@ -22,7 +22,7 @@ checks. ## New `modernize` analyzer -Gopls will now report when code could be simplified or clarified by +Gopls now reports when code could be simplified or clarified by using more modern features of Go, and provides a quick fix to apply the change. @@ -31,6 +31,14 @@ Examples: - replacement of conditional assignment using an if/else statement by a call to the `min` or `max` built-in functions added in Go 1.18; +## New `unusedfunc` analyzer + +Gopls now reports unused functions and methods, giving you near +real-time feedback about dead code that may be safely deleted. +Because the analysis is local to each package, only unexported +functions and methods are candidates. +(For a more precise analysis that may report unused exported +functions too, use the `golang.org/x/tools/cmd/deadcode` command.) ## "Implementations" supports generics diff --git a/gopls/internal/analysis/unusedfunc/doc.go b/gopls/internal/analysis/unusedfunc/doc.go new file mode 100644 index 00000000000..5946ed897bb --- /dev/null +++ b/gopls/internal/analysis/unusedfunc/doc.go @@ -0,0 +1,34 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package unusedfunc defines an analyzer that checks for unused +// functions and methods +// +// # Analyzer unusedfunc +// +// unusedfunc: check for unused functions and methods +// +// The unusedfunc analyzer reports functions and methods that are +// never referenced outside of their own declaration. +// +// A function is considered unused if it is unexported and not +// referenced (except within its own declaration). +// +// A method is considered unused if it is unexported, not referenced +// (except within its own declaration), and its name does not match +// that of any method of an interface type declared within the same +// package. +// +// The tool may report a false positive for a declaration of an +// unexported function that is referenced from another package using +// the go:linkname mechanism, if the declaration's doc comment does +// not also have a go:linkname comment. (Such code is in any case +// strongly discouraged: linkname annotations, if they must be used at +// all, should be used on both the declaration and the alias.) +// +// The unusedfunc algorithm is not as precise as the +// golang.org/x/tools/cmd/deadcode tool, but it has the advantage that +// it runs within the modular analysis framework, enabling near +// real-time feedback within gopls. +package unusedfunc diff --git a/gopls/internal/analysis/unusedfunc/main.go b/gopls/internal/analysis/unusedfunc/main.go new file mode 100644 index 00000000000..0f42023b642 --- /dev/null +++ b/gopls/internal/analysis/unusedfunc/main.go @@ -0,0 +1,15 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build ignore + +// The unusedfunc command runs the unusedfunc analyzer. +package main + +import ( + "golang.org/x/tools/go/analysis/singlechecker" + "golang.org/x/tools/gopls/internal/analysis/unusedfunc" +) + +func main() { singlechecker.Main(unusedfunc.Analyzer) } diff --git a/gopls/internal/analysis/unusedfunc/testdata/src/a/a.go b/gopls/internal/analysis/unusedfunc/testdata/src/a/a.go new file mode 100644 index 00000000000..46ccde17d1d --- /dev/null +++ b/gopls/internal/analysis/unusedfunc/testdata/src/a/a.go @@ -0,0 +1,41 @@ +package a + +func main() { + _ = live +} + +// -- functions -- + +func Exported() {} + +func dead() { // want `function "dead" is unused` +} + +func deadRecursive() int { // want `function "deadRecursive" is unused` + return deadRecursive() +} + +func live() {} + +//go:linkname foo +func apparentlyDeadButHasPrecedingLinknameComment() {} + +// -- methods -- + +type ExportedType int +type unexportedType int + +func (ExportedType) Exported() {} +func (unexportedType) Exported() {} + +func (x ExportedType) dead() { // want `method "dead" is unused` + x.dead() +} + +func (u unexportedType) dead() { // want `method "dead" is unused` + u.dead() +} + +func (x ExportedType) dynamic() {} // matches name of interface method => live + +type _ interface{ dynamic() } diff --git a/gopls/internal/analysis/unusedfunc/testdata/src/a/a.go.golden b/gopls/internal/analysis/unusedfunc/testdata/src/a/a.go.golden new file mode 100644 index 00000000000..86da439bf3f --- /dev/null +++ b/gopls/internal/analysis/unusedfunc/testdata/src/a/a.go.golden @@ -0,0 +1,26 @@ +package a + +func main() { + _ = live +} + +// -- functions -- + +func Exported() {} + +func live() {} + +//go:linkname foo +func apparentlyDeadButHasPrecedingLinknameComment() {} + +// -- methods -- + +type ExportedType int +type unexportedType int + +func (ExportedType) Exported() {} +func (unexportedType) Exported() {} + +func (x ExportedType) dynamic() {} // matches name of interface method => live + +type _ interface{ dynamic() } diff --git a/gopls/internal/analysis/unusedfunc/unusedfunc.go b/gopls/internal/analysis/unusedfunc/unusedfunc.go new file mode 100644 index 00000000000..f13da635890 --- /dev/null +++ b/gopls/internal/analysis/unusedfunc/unusedfunc.go @@ -0,0 +1,183 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package unusedfunc + +import ( + _ "embed" + "fmt" + "go/ast" + "go/types" + "strings" + + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/analysis/passes/inspect" + "golang.org/x/tools/go/ast/inspector" + "golang.org/x/tools/gopls/internal/util/astutil" + "golang.org/x/tools/internal/analysisinternal" +) + +// Assumptions +// +// Like unusedparams, this analyzer depends on the invariant of the +// gopls analysis driver that only the "widest" package (the one with +// the most files) for a given file is analyzed. This invariant allows +// the algorithm to make "closed world" assumptions about the target +// package. (In general, analysis of Go test packages cannot make that +// assumption because in-package tests add new files to existing +// packages, potentially invalidating results.) Consequently, running +// this analyzer in, say, unitchecker or multichecker may produce +// incorrect results. +// +// A function is unreferenced if it is never referenced except within +// its own declaration, and it is unexported. (Exported functions must +// be assumed to be referenced from other packages.) +// +// For methods, we assume that the receiver type is "live" (variables +// of that type are created) and "address taken" (its rtype ends up in +// an at least one interface value). This means exported methods may +// be called via reflection or by interfaces defined in other +// packages, so again we are concerned only with unexported methods. +// +// To discount the possibility of a method being called via an +// interface, we must additionally ensure that no literal interface +// type within the package has a method of the same name. +// (Unexported methods cannot be called through interfaces declared +// in other packages because each package has a private namespace +// for unexported identifiers.) + +//go:embed doc.go +var doc string + +var Analyzer = &analysis.Analyzer{ + Name: "unusedfunc", + Doc: analysisinternal.MustExtractDoc(doc, "unusedfunc"), + Requires: []*analysis.Analyzer{inspect.Analyzer}, + Run: run, + URL: "https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/unusedfunc", +} + +func run(pass *analysis.Pass) (any, error) { + inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) + + // Gather names of unexported interface methods declared in this package. + localIfaceMethods := make(map[string]bool) + nodeFilter := []ast.Node{(*ast.InterfaceType)(nil)} + inspect.Preorder(nodeFilter, func(n ast.Node) { + iface := n.(*ast.InterfaceType) + for _, field := range iface.Methods.List { + if len(field.Names) > 0 { + id := field.Names[0] + if !id.IsExported() { + // TODO(adonovan): check not just name but signature too. + localIfaceMethods[id.Name] = true + } + } + } + }) + + // Map each function/method symbol to its declaration. + decls := make(map[*types.Func]*ast.FuncDecl) + for _, file := range pass.Files { + if ast.IsGenerated(file) { + continue // skip generated files + } + + for _, decl := range file.Decls { + if decl, ok := decl.(*ast.FuncDecl); ok { + id := decl.Name + // Exported functions may be called from other packages. + if id.IsExported() { + continue + } + + // Blank functions are exempt from diagnostics. + if id.Name == "_" { + continue + } + + // An (unexported) method whose name matches an + // interface method declared in the same package + // may be dynamically called via that interface. + if decl.Recv != nil && localIfaceMethods[id.Name] { + continue + } + + // main and init functions are implicitly always used + if decl.Recv == nil && (id.Name == "init" || id.Name == "main") { + continue + } + + fn := pass.TypesInfo.Defs[id].(*types.Func) + decls[fn] = decl + } + } + } + + // Scan for uses of each function symbol. + // (Ignore uses within the function's body.) + use := func(ref ast.Node, obj types.Object) { + if fn, ok := obj.(*types.Func); ok { + if fn := fn.Origin(); fn.Pkg() == pass.Pkg { + if decl, ok := decls[fn]; ok { + // Ignore uses within the function's body. + if decl.Body != nil && astutil.NodeContains(decl.Body, ref.Pos()) { + return + } + delete(decls, fn) // symbol is referenced + } + } + } + } + for id, obj := range pass.TypesInfo.Uses { + use(id, obj) + } + for sel, seln := range pass.TypesInfo.Selections { + use(sel, seln.Obj()) + } + + // Report the remaining unreferenced symbols. +nextDecl: + for fn, decl := range decls { + noun := "function" + if decl.Recv != nil { + noun = "method" + } + + pos := decl.Pos() // start of func decl or associated comment + if decl.Doc != nil { + pos = decl.Doc.Pos() + + // Skip if there's a preceding //go:linkname directive. + // + // (A program can link fine without such a directive, + // but it is bad style; and the directive may + // appear anywhere, not just on the preceding line, + // but again that is poor form.) + // + // TODO(adonovan): use ast.ParseDirective when #68021 lands. + for _, comment := range decl.Doc.List { + if strings.HasPrefix(comment.Text, "//go:linkname ") { + continue nextDecl + } + } + } + + pass.Report(analysis.Diagnostic{ + Pos: decl.Name.Pos(), + End: decl.Name.End(), + Message: fmt.Sprintf("%s %q is unused", noun, fn.Name()), + SuggestedFixes: []analysis.SuggestedFix{{ + Message: fmt.Sprintf("Delete %s %q", noun, fn.Name()), + TextEdits: []analysis.TextEdit{{ + // delete declaration + Pos: pos, + End: decl.End(), + }}, + }}, + }) + } + + return nil, nil +} diff --git a/gopls/internal/analysis/unusedfunc/unusedfunc_test.go b/gopls/internal/analysis/unusedfunc/unusedfunc_test.go new file mode 100644 index 00000000000..1bf73da3653 --- /dev/null +++ b/gopls/internal/analysis/unusedfunc/unusedfunc_test.go @@ -0,0 +1,17 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package unusedfunc_test + +import ( + "testing" + + "golang.org/x/tools/go/analysis/analysistest" + "golang.org/x/tools/gopls/internal/analysis/unusedfunc" +) + +func Test(t *testing.T) { + testdata := analysistest.TestData() + analysistest.RunWithSuggestedFixes(t, testdata, unusedfunc.Analyzer, "a") +} diff --git a/gopls/internal/doc/api.json b/gopls/internal/doc/api.json index acf79766401..982ec34909b 100644 --- a/gopls/internal/doc/api.json +++ b/gopls/internal/doc/api.json @@ -585,6 +585,11 @@ "Doc": "check for invalid conversions of uintptr to unsafe.Pointer\n\nThe unsafeptr analyzer reports likely incorrect uses of unsafe.Pointer\nto convert integers to pointers. A conversion from uintptr to\nunsafe.Pointer is invalid if it implies that there is a uintptr-typed\nword in memory that holds a pointer value, because that word will be\ninvisible to stack copying and to the garbage collector.", "Default": "true" }, + { + "Name": "\"unusedfunc\"", + "Doc": "check for unused functions and methods\n\nThe unusedfunc analyzer reports functions and methods that are\nnever referenced outside of their own declaration.\n\nA function is considered unused if it is unexported and not\nreferenced (except within its own declaration).\n\nA method is considered unused if it is unexported, not referenced\n(except within its own declaration), and its name does not match\nthat of any method of an interface type declared within the same\npackage.\n\nThe tool may report a false positive for a declaration of an\nunexported function that is referenced from another package using\nthe go:linkname mechanism, if the declaration's doc comment does\nnot also have a go:linkname comment. (Such code is in any case\nstrongly discouraged: linkname annotations, if they must be used at\nall, should be used on both the declaration and the alias.)\n\nThe unusedfunc algorithm is not as precise as the\ngolang.org/x/tools/cmd/deadcode tool, but it has the advantage that\nit runs within the modular analysis framework, enabling near\nreal-time feedback within gopls.", + "Default": "true" + }, { "Name": "\"unusedparams\"", "Doc": "check for unused parameters of functions\n\nThe unusedparams analyzer checks functions to see if there are\nany parameters that are not being used.\n\nTo ensure soundness, it ignores:\n - \"address-taken\" functions, that is, functions that are used as\n a value rather than being called directly; their signatures may\n be required to conform to a func type.\n - exported functions or methods, since they may be address-taken\n in another package.\n - unexported methods whose name matches an interface method\n declared in the same package, since the method's signature\n may be required to conform to the interface type.\n - functions with empty bodies, or containing just a call to panic.\n - parameters that are unnamed, or named \"_\", the blank identifier.\n\nThe analyzer suggests a fix of replacing the parameter name by \"_\",\nbut in such cases a deeper fix can be obtained by invoking the\n\"Refactor: remove unused parameter\" code action, which will\neliminate the parameter entirely, along with all corresponding\narguments at call sites, while taking care to preserve any side\neffects in the argument expressions; see\nhttps://github.com/golang/tools/releases/tag/gopls%2Fv0.14.", @@ -1229,6 +1234,12 @@ "URL": "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/unsafeptr", "Default": true }, + { + "Name": "unusedfunc", + "Doc": "check for unused functions and methods\n\nThe unusedfunc analyzer reports functions and methods that are\nnever referenced outside of their own declaration.\n\nA function is considered unused if it is unexported and not\nreferenced (except within its own declaration).\n\nA method is considered unused if it is unexported, not referenced\n(except within its own declaration), and its name does not match\nthat of any method of an interface type declared within the same\npackage.\n\nThe tool may report a false positive for a declaration of an\nunexported function that is referenced from another package using\nthe go:linkname mechanism, if the declaration's doc comment does\nnot also have a go:linkname comment. (Such code is in any case\nstrongly discouraged: linkname annotations, if they must be used at\nall, should be used on both the declaration and the alias.)\n\nThe unusedfunc algorithm is not as precise as the\ngolang.org/x/tools/cmd/deadcode tool, but it has the advantage that\nit runs within the modular analysis framework, enabling near\nreal-time feedback within gopls.", + "URL": "https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/unusedfunc", + "Default": true + }, { "Name": "unusedparams", "Doc": "check for unused parameters of functions\n\nThe unusedparams analyzer checks functions to see if there are\nany parameters that are not being used.\n\nTo ensure soundness, it ignores:\n - \"address-taken\" functions, that is, functions that are used as\n a value rather than being called directly; their signatures may\n be required to conform to a func type.\n - exported functions or methods, since they may be address-taken\n in another package.\n - unexported methods whose name matches an interface method\n declared in the same package, since the method's signature\n may be required to conform to the interface type.\n - functions with empty bodies, or containing just a call to panic.\n - parameters that are unnamed, or named \"_\", the blank identifier.\n\nThe analyzer suggests a fix of replacing the parameter name by \"_\",\nbut in such cases a deeper fix can be obtained by invoking the\n\"Refactor: remove unused parameter\" code action, which will\neliminate the parameter entirely, along with all corresponding\narguments at call sites, while taking care to preserve any side\neffects in the argument expressions; see\nhttps://github.com/golang/tools/releases/tag/gopls%2Fv0.14.", diff --git a/gopls/internal/golang/change_signature.go b/gopls/internal/golang/change_signature.go index 8157c6d03fb..e9fc099399d 100644 --- a/gopls/internal/golang/change_signature.go +++ b/gopls/internal/golang/change_signature.go @@ -755,10 +755,6 @@ func reTypeCheck(logf func(string, ...any), orig *cache.Package, fileMask map[pr // TODO(golang/go#63472): this looks wrong with the new Go version syntax. var goVersionRx = regexp.MustCompile(`^go([1-9][0-9]*)\.(0|[1-9][0-9]*)$`) -func remove[T any](s []T, i int) []T { - return append(s[:i], s[i+1:]...) -} - // selectElements returns a new array of elements of s indicated by the // provided list of indices. It returns false if any index was out of bounds. // diff --git a/gopls/internal/server/diagnostics.go b/gopls/internal/server/diagnostics.go index b5699c84de4..e95bf297501 100644 --- a/gopls/internal/server/diagnostics.go +++ b/gopls/internal/server/diagnostics.go @@ -433,7 +433,7 @@ func (s *server) diagnose(ctx context.Context, snapshot *cache.Snapshot) (diagMa // For analysis, we use the *widest* package for each open file, // for two reasons: // - // - Correctness: some analyzers (e.g. unusedparam) depend + // - Correctness: some analyzers (e.g. unused{param,func}) depend // on it. If applied to a non-test package for which a // corresponding test package exists, they make assumptions // that are falsified in the test package, for example that diff --git a/gopls/internal/settings/analysis.go b/gopls/internal/settings/analysis.go index 1bbb1d54797..7e13c801a85 100644 --- a/gopls/internal/settings/analysis.go +++ b/gopls/internal/settings/analysis.go @@ -56,6 +56,7 @@ import ( "golang.org/x/tools/gopls/internal/analysis/simplifycompositelit" "golang.org/x/tools/gopls/internal/analysis/simplifyrange" "golang.org/x/tools/gopls/internal/analysis/simplifyslice" + "golang.org/x/tools/gopls/internal/analysis/unusedfunc" "golang.org/x/tools/gopls/internal/analysis/unusedparams" "golang.org/x/tools/gopls/internal/analysis/unusedvariable" "golang.org/x/tools/gopls/internal/analysis/yield" @@ -171,6 +172,7 @@ func init() { // other simplifiers: {analyzer: infertypeargs.Analyzer, enabled: true, severity: protocol.SeverityHint}, {analyzer: unusedparams.Analyzer, enabled: true}, + {analyzer: unusedfunc.Analyzer, enabled: true}, {analyzer: unusedwrite.Analyzer, enabled: true}, // uses go/ssa // type-error analyzers diff --git a/gopls/internal/test/integration/diagnostics/diagnostics_test.go b/gopls/internal/test/integration/diagnostics/diagnostics_test.go index 0b8895b3d31..9e6c504cc86 100644 --- a/gopls/internal/test/integration/diagnostics/diagnostics_test.go +++ b/gopls/internal/test/integration/diagnostics/diagnostics_test.go @@ -79,7 +79,7 @@ go 1.12 ).Run(t, onlyMod, func(t *testing.T, env *Env) { env.CreateBuffer("main.go", `package main -func m() { +func _() { log.Println() } `) diff --git a/gopls/internal/test/integration/diagnostics/gopackagesdriver_test.go b/gopls/internal/test/integration/diagnostics/gopackagesdriver_test.go index 65700b69795..3e7c0f5f2fd 100644 --- a/gopls/internal/test/integration/diagnostics/gopackagesdriver_test.go +++ b/gopls/internal/test/integration/diagnostics/gopackagesdriver_test.go @@ -25,9 +25,6 @@ go 1.12 package foo import "mod.com/hello" - -func f() { -} ` WithOptions( FakeGoPackagesDriver(t), diff --git a/gopls/internal/test/integration/misc/compileropt_test.go b/gopls/internal/test/integration/misc/compileropt_test.go index e066306e229..8b8f78cd62d 100644 --- a/gopls/internal/test/integration/misc/compileropt_test.go +++ b/gopls/internal/test/integration/misc/compileropt_test.go @@ -63,7 +63,7 @@ func main() { // edited buffers, thanks to the magic of overlays. env.SetBufferContent("main.go", ` package main -func main() {} +func main() { _ = f } func f(x int) *int { return &x }`) env.AfterChange(Diagnostics( ForFile("main.go"), diff --git a/gopls/internal/test/integration/misc/imports_test.go b/gopls/internal/test/integration/misc/imports_test.go index 30a161017dc..5b8b020124d 100644 --- a/gopls/internal/test/integration/misc/imports_test.go +++ b/gopls/internal/test/integration/misc/imports_test.go @@ -66,7 +66,7 @@ package main import "fmt" //this comment is necessary for failure -func a() { +func _() { fmt.Println("hello") } ` @@ -96,7 +96,7 @@ func f(x float64) float64 { -- b.go -- package foo -func g() { +func _() { _ = rand.Int63() } ` diff --git a/gopls/internal/test/marker/testdata/callhierarchy/issue64451.txt b/gopls/internal/test/marker/testdata/callhierarchy/issue64451.txt index 618d6ed6e34..3e6928e6f1d 100644 --- a/gopls/internal/test/marker/testdata/callhierarchy/issue64451.txt +++ b/gopls/internal/test/marker/testdata/callhierarchy/issue64451.txt @@ -13,7 +13,7 @@ go 1.0 -- a/a.go -- package a -func foo() { //@ loc(foo, "foo") +func Foo() { //@ loc(Foo, "Foo") bar() } @@ -38,14 +38,14 @@ func init() { //@ loc(init, "init") baz() } -//@ outgoingcalls(foo, bar) +//@ outgoingcalls(Foo, bar) //@ outgoingcalls(bar, baz) //@ outgoingcalls(baz, bluh) //@ outgoingcalls(bluh) //@ outgoingcalls(init, baz) -//@ incomingcalls(foo) -//@ incomingcalls(bar, foo) +//@ incomingcalls(Foo) +//@ incomingcalls(bar, Foo) //@ incomingcalls(baz, bar, global, init) //@ incomingcalls(bluh, baz) //@ incomingcalls(init) diff --git a/gopls/internal/test/marker/testdata/codeaction/extract_variable-toplevel.txt b/gopls/internal/test/marker/testdata/codeaction/extract_variable-toplevel.txt index d41fee42c9f..00d3bc6983e 100644 --- a/gopls/internal/test/marker/testdata/codeaction/extract_variable-toplevel.txt +++ b/gopls/internal/test/marker/testdata/codeaction/extract_variable-toplevel.txt @@ -10,7 +10,7 @@ var slice = append([]int{}, 1, 2, 3) //@codeaction("[]int{}", "refactor.extract. type SHA256 [32]byte //@codeaction("32", "refactor.extract.constant", edit=arraylen) -func f([2]int) {} //@codeaction("2", "refactor.extract.constant", edit=paramtypearraylen) +func F([2]int) {} //@codeaction("2", "refactor.extract.constant", edit=paramtypearraylen) -- @lenhello/a.go -- @@ -3 +3,2 @@ @@ -29,9 +29,9 @@ func f([2]int) {} //@codeaction("2", "refactor.extract.constant", edit=paramtype +type SHA256 [newConst]byte //@codeaction("32", "refactor.extract.constant", edit=arraylen) -- @paramtypearraylen/a.go -- @@ -9 +9,2 @@ --func f([2]int) {} //@codeaction("2", "refactor.extract.constant", edit=paramtypearraylen) +-func F([2]int) {} //@codeaction("2", "refactor.extract.constant", edit=paramtypearraylen) +const newConst = 2 -+func f([newConst]int) {} //@codeaction("2", "refactor.extract.constant", edit=paramtypearraylen) ++func F([newConst]int) {} //@codeaction("2", "refactor.extract.constant", edit=paramtypearraylen) -- b/b.go -- package b diff --git a/gopls/internal/test/marker/testdata/codeaction/functionextraction.txt b/gopls/internal/test/marker/testdata/codeaction/functionextraction.txt index f84eeae7b4c..73276cbd03b 100644 --- a/gopls/internal/test/marker/testdata/codeaction/functionextraction.txt +++ b/gopls/internal/test/marker/testdata/codeaction/functionextraction.txt @@ -367,6 +367,8 @@ func newFunction1() int { return 1 } +var _ = newFunction1 + -- @scope/scope.go -- package extract @@ -385,6 +387,8 @@ func newFunction1() int { return 1 } +var _ = newFunction1 + -- smart_initialization.go -- package extract diff --git a/gopls/internal/test/marker/testdata/codeaction/functionextraction_issue50851.txt b/gopls/internal/test/marker/testdata/codeaction/functionextraction_issue50851.txt index b085559cf2a..52a4b412055 100644 --- a/gopls/internal/test/marker/testdata/codeaction/functionextraction_issue50851.txt +++ b/gopls/internal/test/marker/testdata/codeaction/functionextraction_issue50851.txt @@ -6,7 +6,7 @@ package main type F struct{} -func (f *F) func1() { +func (f *F) _() { println("a") println("b") //@ codeaction("print", "refactor.extract.function", end=end, result=result) @@ -20,7 +20,7 @@ package main type F struct{} -func (f *F) func1() { +func (f *F) _() { println("a") newFunction() //@loc(end, ")") diff --git a/gopls/internal/test/marker/testdata/codeaction/grouplines.txt b/gopls/internal/test/marker/testdata/codeaction/grouplines.txt index 766b13b7f56..4817d8d7241 100644 --- a/gopls/internal/test/marker/testdata/codeaction/grouplines.txt +++ b/gopls/internal/test/marker/testdata/codeaction/grouplines.txt @@ -79,7 +79,7 @@ package func_call import "fmt" -func a() { +func F() { fmt.Println( 1 /*@codeaction("1", "refactor.rewrite.joinLines", result=func_call)*/, 2, @@ -93,7 +93,7 @@ package func_call import "fmt" -func a() { +func F() { fmt.Println(1 /*@codeaction("1", "refactor.rewrite.joinLines", result=func_call)*/, 2, 3, fmt.Sprintf("hello %d", 4)) } @@ -102,7 +102,7 @@ package indent import "fmt" -func a() { +func F() { fmt.Println( 1, 2, @@ -118,7 +118,7 @@ package indent import "fmt" -func a() { +func F() { fmt.Println( 1, 2, @@ -134,7 +134,7 @@ type A struct{ b int } -func a() { +func F() { _ = A{ a: 1, b: 2 /*@codeaction("b", "refactor.rewrite.joinLines", result=structelts)*/, @@ -149,14 +149,14 @@ type A struct{ b int } -func a() { +func F() { _ = A{a: 1, b: 2 /*@codeaction("b", "refactor.rewrite.joinLines", result=structelts)*/} } -- sliceelts/sliceelts.go -- package sliceelts -func a() { +func F() { _ = []int{ 1 /*@codeaction("1", "refactor.rewrite.joinLines", result=sliceelts)*/, 2, @@ -166,14 +166,14 @@ func a() { -- @sliceelts/sliceelts/sliceelts.go -- package sliceelts -func a() { +func F() { _ = []int{1 /*@codeaction("1", "refactor.rewrite.joinLines", result=sliceelts)*/, 2} } -- mapelts/mapelts.go -- package mapelts -func a() { +func F() { _ = map[string]int{ "a": 1 /*@codeaction("1", "refactor.rewrite.joinLines", result=mapelts)*/, "b": 2, @@ -183,7 +183,7 @@ func a() { -- @mapelts/mapelts/mapelts.go -- package mapelts -func a() { +func F() { _ = map[string]int{"a": 1 /*@codeaction("1", "refactor.rewrite.joinLines", result=mapelts)*/, "b": 2} } diff --git a/gopls/internal/test/marker/testdata/codeaction/inline_issue67336.txt b/gopls/internal/test/marker/testdata/codeaction/inline_issue67336.txt index daae6e41144..437fb474fb2 100644 --- a/gopls/internal/test/marker/testdata/codeaction/inline_issue67336.txt +++ b/gopls/internal/test/marker/testdata/codeaction/inline_issue67336.txt @@ -44,7 +44,7 @@ const ( someConst = 5 ) -func foo() { +func _() { inlined.Baz(context.TODO()) //@ codeaction("Baz", "refactor.inline.call", result=inline) pkg.Bar() } @@ -65,7 +65,7 @@ const ( someConst = 5 ) -func foo() { +func _() { var _ context.Context = context.TODO() pkg0.Foo(typ.T(5)) //@ codeaction("Baz", "refactor.inline.call", result=inline) pkg.Bar() diff --git a/gopls/internal/test/marker/testdata/codeaction/inline_issue68554.txt b/gopls/internal/test/marker/testdata/codeaction/inline_issue68554.txt index 49b18b27935..868b30fce85 100644 --- a/gopls/internal/test/marker/testdata/codeaction/inline_issue68554.txt +++ b/gopls/internal/test/marker/testdata/codeaction/inline_issue68554.txt @@ -8,7 +8,7 @@ import ( "io" ) -func f(d discard) { +func _(d discard) { g(d) //@codeaction("g", "refactor.inline.call", result=out) } @@ -25,7 +25,7 @@ import ( "io" ) -func f(d discard) { +func _(d discard) { fmt.Println(d) //@codeaction("g", "refactor.inline.call", result=out) } diff --git a/gopls/internal/test/marker/testdata/codeaction/removeparam.txt b/gopls/internal/test/marker/testdata/codeaction/removeparam.txt index c8fddb0fff7..8bebfc29c40 100644 --- a/gopls/internal/test/marker/testdata/codeaction/removeparam.txt +++ b/gopls/internal/test/marker/testdata/codeaction/removeparam.txt @@ -60,6 +60,8 @@ func _() { a.A(f(), 1) } +var _ = g + -- @a/a/a2.go -- package a @@ -96,6 +98,8 @@ func g() int { func _() { a.A(f()) } + +var _ = g -- field/field.go -- package field diff --git a/gopls/internal/test/marker/testdata/codeaction/removeparam_resolve.txt b/gopls/internal/test/marker/testdata/codeaction/removeparam_resolve.txt index b51dd6fb8cf..3d92d758b13 100644 --- a/gopls/internal/test/marker/testdata/codeaction/removeparam_resolve.txt +++ b/gopls/internal/test/marker/testdata/codeaction/removeparam_resolve.txt @@ -71,6 +71,8 @@ func _() { a.A(f(), 1) } +var _ = g + -- @a/a/a2.go -- package a @@ -107,6 +109,8 @@ func g() int { func _() { a.A(f()) } + +var _ = g -- field/field.go -- package field diff --git a/gopls/internal/test/marker/testdata/codeaction/splitlines.txt b/gopls/internal/test/marker/testdata/codeaction/splitlines.txt index f0f6ef6091c..65178715bb0 100644 --- a/gopls/internal/test/marker/testdata/codeaction/splitlines.txt +++ b/gopls/internal/test/marker/testdata/codeaction/splitlines.txt @@ -79,7 +79,7 @@ package func_call import "fmt" -func a() { +func F() { fmt.Println(1, 2, 3, fmt.Sprintf("hello %d", 4)) //@codeaction("1", "refactor.rewrite.splitLines", result=func_call) } @@ -88,7 +88,7 @@ package func_call import "fmt" -func a() { +func F() { fmt.Println( 1, 2, @@ -102,7 +102,7 @@ package indent import "fmt" -func a() { +func F() { fmt.Println(1, 2, 3, fmt.Sprintf("hello %d", 4)) //@codeaction("hello", "refactor.rewrite.splitLines", result=indent) } @@ -111,7 +111,7 @@ package indent import "fmt" -func a() { +func F() { fmt.Println(1, 2, 3, fmt.Sprintf( "hello %d", 4, @@ -123,7 +123,7 @@ package indent2 import "fmt" -func a() { +func F() { fmt. Println(1, 2, 3, fmt.Sprintf("hello %d", 4)) //@codeaction("1", "refactor.rewrite.splitLines", result=indent2) } @@ -133,7 +133,7 @@ package indent2 import "fmt" -func a() { +func F() { fmt. Println( 1, @@ -151,7 +151,7 @@ type A struct{ b int } -func a() { +func F() { _ = A{a: 1, b: 2} //@codeaction("b", "refactor.rewrite.splitLines", result=structelts) } @@ -163,7 +163,7 @@ type A struct{ b int } -func a() { +func F() { _ = A{ a: 1, b: 2, @@ -173,14 +173,14 @@ func a() { -- sliceelts/sliceelts.go -- package sliceelts -func a() { +func F() { _ = []int{1, 2} //@codeaction("1", "refactor.rewrite.splitLines", result=sliceelts) } -- @sliceelts/sliceelts/sliceelts.go -- package sliceelts -func a() { +func F() { _ = []int{ 1, 2, @@ -190,14 +190,14 @@ func a() { -- mapelts/mapelts.go -- package mapelts -func a() { +func F() { _ = map[string]int{"a": 1, "b": 2} //@codeaction("1", "refactor.rewrite.splitLines", result=mapelts) } -- @mapelts/mapelts/mapelts.go -- package mapelts -func a() { +func F() { _ = map[string]int{ "a": 1, "b": 2, diff --git a/gopls/internal/test/marker/testdata/codelens/test.txt b/gopls/internal/test/marker/testdata/codelens/test.txt index ba68cf019df..60d573a81e5 100644 --- a/gopls/internal/test/marker/testdata/codelens/test.txt +++ b/gopls/internal/test/marker/testdata/codelens/test.txt @@ -30,3 +30,9 @@ func BenchmarkFuncWithCodeLens(b *testing.B) { //@codelens(re"()func", "run benc } func helper() {} // expect no code lens + +func _() { + // pacify unusedfunc + thisShouldNotHaveACodeLens(nil) + helper() +} diff --git a/gopls/internal/test/marker/testdata/definition/branch.txt b/gopls/internal/test/marker/testdata/definition/branch.txt index 814a83d43b4..e80c83a92ae 100644 --- a/gopls/internal/test/marker/testdata/definition/branch.txt +++ b/gopls/internal/test/marker/testdata/definition/branch.txt @@ -10,7 +10,7 @@ package a import "log" -func breakLoop() { +func BreakLoop() { for i := 0; i < 10; i++ { if i > 6 { break //@def("break", rbrace1) @@ -18,7 +18,7 @@ func breakLoop() { } //@loc(rbrace1, `}`) } -func breakNestedLoop() { +func BreakNestedLoop() { for i := 0; i < 10; i++ { for j := 0; j < 5; j++ { if j > 1 { @@ -28,7 +28,7 @@ func breakNestedLoop() { } } -func breakNestedLoopWithLabel() { +func BreakNestedLoopWithLabel() { Outer: for i := 0; i < 10; i++ { for j := 0; j < 5; j++ { @@ -39,7 +39,7 @@ func breakNestedLoopWithLabel() { } //@loc(outerparen, `}`) } -func breakSwitch(i int) { +func BreakSwitch(i int) { switch i { case 1: break //@def("break", rbrace4) @@ -50,7 +50,7 @@ func breakSwitch(i int) { } //@loc(rbrace4, `}`) } -func breakSwitchLabel(i int) { +func BreakSwitchLabel(i int) { loop: for { switch i { @@ -64,7 +64,7 @@ loop: } //@loc(loopparen, `}`) } -func breakSelect(c, quit chan int) { +func BreakSelect(c, quit chan int) { x, y := 0, 1 for { select { @@ -78,7 +78,7 @@ func breakSelect(c, quit chan int) { } } -func breakWithContinue() { +func BreakWithContinue() { for j := 0; j < 5; j++ { if (j < 4) { continue @@ -87,7 +87,7 @@ func breakWithContinue() { } //@loc(rbrace6, `}`) } -func gotoNestedLoop() { +func GotoNestedLoop() { Outer: //@loc(outer, "Outer") for i := 0; i < 10; i++ { for j := 0; j < 5; j++ { @@ -98,7 +98,7 @@ func gotoNestedLoop() { } } -func continueLoop() { +func ContinueLoop() { for j := 0; j < 5; j++ { //@loc(for3, `for`) if (j < 4) { continue //@def("continue", for3) @@ -107,7 +107,7 @@ func continueLoop() { } } -func continueDoubleLoop() { +func ContinueDoubleLoop() { for i := 0; i < 10; i++ { //@loc(for4, `for`) for j := 0; j < 5; j++ { if (j > 1) { @@ -120,7 +120,7 @@ func continueDoubleLoop() { } } -func breakInBlockStmt() { +func BreakInBlockStmt() { for { if 0 < 10 { { @@ -130,7 +130,7 @@ func breakInBlockStmt() { } //@loc(rbrace9, `}`) } -func breakInLabeledStmt() { +func BreakInLabeledStmt() { outer: for { goto inner @@ -139,7 +139,7 @@ func breakInLabeledStmt() { } //@loc(for5, `}`) } -func breakToLabel(n int) { +func BreakToLabel(n int) { outer1: switch n { case 1: @@ -152,7 +152,7 @@ func breakToLabel(n int) { } //@loc(outer1, "}") } -func continueToLabel(n int) { +func ContinueToLabel(n int) { outer1: for { //@loc(outer2, "for") switch n { diff --git a/gopls/internal/test/marker/testdata/foldingrange/a.txt b/gopls/internal/test/marker/testdata/foldingrange/a.txt index 468f9b02fa6..864442e1b0c 100644 --- a/gopls/internal/test/marker/testdata/foldingrange/a.txt +++ b/gopls/internal/test/marker/testdata/foldingrange/a.txt @@ -12,9 +12,9 @@ import ( import _ "os" -// bar is a function. +// Bar is a function. // With a multiline doc comment. -func bar() ( +func Bar() ( string, ) { /* This is a single line comment */ @@ -143,9 +143,9 @@ import (<0 kind="imports"> import _ "os" -// bar is a function.<1 kind="comment"> +// Bar is a function.<1 kind="comment"> // With a multiline doc comment. -func bar() (<2 kind=""> +func Bar() (<2 kind=""> string, ) {<3 kind=""> /* This is a single line comment */ diff --git a/gopls/internal/test/marker/testdata/foldingrange/a_lineonly.txt b/gopls/internal/test/marker/testdata/foldingrange/a_lineonly.txt index cdae6f2245e..909dbc814bf 100644 --- a/gopls/internal/test/marker/testdata/foldingrange/a_lineonly.txt +++ b/gopls/internal/test/marker/testdata/foldingrange/a_lineonly.txt @@ -21,9 +21,9 @@ import ( import _ "os" -// bar is a function. +// Bar is a function. // With a multiline doc comment. -func bar() string { +func Bar() string { /* This is a single line comment */ switch { case true: @@ -150,9 +150,9 @@ import (<0 kind="imports"> import _ "os" -// bar is a function.<1 kind="comment"> +// Bar is a function.<1 kind="comment"> // With a multiline doc comment. -func bar() string {<2 kind=""> +func Bar() string {<2 kind=""> /* This is a single line comment */ switch {<3 kind=""> case true:<4 kind=""> diff --git a/gopls/internal/test/marker/testdata/foldingrange/bad.txt b/gopls/internal/test/marker/testdata/foldingrange/bad.txt index 14444e7aa44..fa18f1bc2c2 100644 --- a/gopls/internal/test/marker/testdata/foldingrange/bad.txt +++ b/gopls/internal/test/marker/testdata/foldingrange/bad.txt @@ -11,8 +11,8 @@ import ( "fmt" import ( _ "os" ) -// badBar is a function. -func badBar() string { x := true +// BadBar is a function. +func BadBar() string { x := true if x { // This is the only foldable thing in this file when lineFoldingOnly fmt.Println("true") @@ -30,8 +30,8 @@ import (<0 kind="imports"> "fmt" import (<1 kind="imports"> _ "os" ) -// badBar is a function. -func badBar() string {<2 kind=""> x := true +// BadBar is a function. +func BadBar() string {<2 kind=""> x := true if x {<3 kind=""> // This is the only foldable thing in this file when lineFoldingOnly fmt.Println(<4 kind="">"true") diff --git a/gopls/internal/test/marker/testdata/highlight/highlight_kind.txt b/gopls/internal/test/marker/testdata/highlight/highlight_kind.txt index bd059f77450..880e5bd720e 100644 --- a/gopls/internal/test/marker/testdata/highlight/highlight_kind.txt +++ b/gopls/internal/test/marker/testdata/highlight/highlight_kind.txt @@ -15,7 +15,7 @@ type MyMap map[string]string type NestMap map[Nest]Nest -func highlightTest() { +func _() { const constIdent = 1 //@hiloc(constIdent, "constIdent", write) //@highlightall(constIdent) var varNoInit int //@hiloc(varNoInit, "varNoInit", write) diff --git a/gopls/internal/test/marker/testdata/hover/comment.txt b/gopls/internal/test/marker/testdata/hover/comment.txt index 86a268f5981..c6eddf37962 100644 --- a/gopls/internal/test/marker/testdata/hover/comment.txt +++ b/gopls/internal/test/marker/testdata/hover/comment.txt @@ -25,8 +25,8 @@ func Conv(s string) int { return int(i) } -// unsafeConv converts s to a byte slice using [unsafe.Pointer]. hover("Pointer", "Pointer", unsafePointer) -func unsafeConv(s string) []byte { +// UnsafeConv converts s to a byte slice using [unsafe.Pointer]. hover("Pointer", "Pointer", unsafePointer) +func UnsafeConv(s string) []byte { p := unsafe.StringData(s) b := unsafe.Slice(p, len(s)) return b diff --git a/gopls/internal/test/marker/testdata/hover/embed.txt b/gopls/internal/test/marker/testdata/hover/embed.txt index 3f4086c2332..2abc25bfcad 100644 --- a/gopls/internal/test/marker/testdata/hover/embed.txt +++ b/gopls/internal/test/marker/testdata/hover/embed.txt @@ -34,6 +34,8 @@ func (P) m() {} var p P //@hover("P", "P", P) +var _ = P.m + -- @P -- ```go type P struct { diff --git a/gopls/internal/test/marker/testdata/hover/linkable.txt b/gopls/internal/test/marker/testdata/hover/linkable.txt index 6dc8076523e..e5d2efe8480 100644 --- a/gopls/internal/test/marker/testdata/hover/linkable.txt +++ b/gopls/internal/test/marker/testdata/hover/linkable.txt @@ -36,6 +36,8 @@ func (T) M() {} // m is not exported, and so should not be linkable. func (T) m() {} +var _ = T.m + func _() { var t T diff --git a/gopls/internal/test/marker/testdata/hover/linkname.txt b/gopls/internal/test/marker/testdata/hover/linkname.txt index 6e128a2f215..2633506eac7 100644 --- a/gopls/internal/test/marker/testdata/hover/linkname.txt +++ b/gopls/internal/test/marker/testdata/hover/linkname.txt @@ -22,6 +22,8 @@ func bar() string { return "foo by bar" } +var _ = bar + -- @bar -- ```go func bar() string diff --git a/gopls/internal/test/marker/testdata/hover/methods.txt b/gopls/internal/test/marker/testdata/hover/methods.txt index 142f3ffc97f..402b9274c6a 100644 --- a/gopls/internal/test/marker/testdata/hover/methods.txt +++ b/gopls/internal/test/marker/testdata/hover/methods.txt @@ -28,6 +28,8 @@ func (s S) b() {} func (s *S) PA() {} func (s *S) pb() {} +var _ = (*S).pb + -- a/a.go -- package a diff --git a/gopls/internal/test/marker/testdata/hover/return.txt b/gopls/internal/test/marker/testdata/hover/return.txt index 4b460943cf9..998c7a19d16 100644 --- a/gopls/internal/test/marker/testdata/hover/return.txt +++ b/gopls/internal/test/marker/testdata/hover/return.txt @@ -3,10 +3,10 @@ This test checks that hovering over a return statement reveals the result type. -- a.go -- package a -func one() int { +func _() int { return 1 //@hover("return", "return 1", "returns (int)") } -func two() (int, int) { +func _() (int, int) { return 1, 2 //@hover("return", "return 1, 2", "returns (int, int)") } diff --git a/gopls/internal/test/marker/testdata/quickfix/embeddirective.txt b/gopls/internal/test/marker/testdata/quickfix/embeddirective.txt index f0915476f7f..124b729868c 100644 --- a/gopls/internal/test/marker/testdata/quickfix/embeddirective.txt +++ b/gopls/internal/test/marker/testdata/quickfix/embeddirective.txt @@ -13,7 +13,7 @@ import ( //go:embed embed.txt //@quickfix("//go:embed", re`must import "embed"`, fix_import) var t string -func unused() { +func _() { _ = os.Stdin _ = io.EOF } diff --git a/gopls/internal/test/marker/testdata/quickfix/self_assignment.txt b/gopls/internal/test/marker/testdata/quickfix/self_assignment.txt index 44a6ad5b8ad..fca3d6d16d7 100644 --- a/gopls/internal/test/marker/testdata/quickfix/self_assignment.txt +++ b/gopls/internal/test/marker/testdata/quickfix/self_assignment.txt @@ -7,7 +7,7 @@ import ( "log" ) -func goodbye() { +func _() { s := "hiiiiiii" s = s //@quickfix("s = s", re"self-assignment", fix) log.Print(s) diff --git a/gopls/internal/test/marker/testdata/rename/basic.txt b/gopls/internal/test/marker/testdata/rename/basic.txt index 618f9593668..73de726e98e 100644 --- a/gopls/internal/test/marker/testdata/rename/basic.txt +++ b/gopls/internal/test/marker/testdata/rename/basic.txt @@ -3,12 +3,12 @@ This test performs basic coverage of 'rename' within a single package. -- basic.go -- package p -func f(x int) { println(x) } //@rename("x", "y", xToy) +func _(x int) { println(x) } //@rename("x", "y", xToy) -- @xToy/basic.go -- @@ -3 +3 @@ --func f(x int) { println(x) } //@rename("x", "y", xToy) -+func f(y int) { println(y) } //@rename("x", "y", xToy) +-func _(x int) { println(x) } //@rename("x", "y", xToy) ++func _(y int) { println(y) } //@rename("x", "y", xToy) -- alias.go -- package p diff --git a/gopls/internal/test/marker/testdata/rename/conflict.txt b/gopls/internal/test/marker/testdata/rename/conflict.txt index 3d7d21cb3e4..9b520a01dad 100644 --- a/gopls/internal/test/marker/testdata/rename/conflict.txt +++ b/gopls/internal/test/marker/testdata/rename/conflict.txt @@ -10,7 +10,7 @@ package super var x int -func f(y int) { +func _(y int) { println(x) println(y) //@renameerr("y", "x", errSuperBlockConflict) } @@ -24,7 +24,7 @@ package sub var a int -func f2(b int) { +func _(b int) { println(a) //@renameerr("a", "b", errSubBlockConflict) println(b) } @@ -32,7 +32,7 @@ func f2(b int) { -- @errSubBlockConflict -- sub/p.go:3:5: renaming this var "a" to "b" sub/p.go:6:10: would cause this reference to become shadowed -sub/p.go:5:9: by this intervening var definition +sub/p.go:5:8: by this intervening var definition -- pkgname/p.go -- package pkgname diff --git a/gopls/internal/test/marker/testdata/rename/crosspkg.txt b/gopls/internal/test/marker/testdata/rename/crosspkg.txt index c60930b0114..76b6ee519eb 100644 --- a/gopls/internal/test/marker/testdata/rename/crosspkg.txt +++ b/gopls/internal/test/marker/testdata/rename/crosspkg.txt @@ -29,6 +29,8 @@ func _() { x.F() //@rename("F", "G", FToG) } +var _ = C.g + -- crosspkg/other/other.go -- package other diff --git a/gopls/internal/test/marker/testdata/rename/generics.txt b/gopls/internal/test/marker/testdata/rename/generics.txt index 0f57570a5fb..61d7801295e 100644 --- a/gopls/internal/test/marker/testdata/rename/generics.txt +++ b/gopls/internal/test/marker/testdata/rename/generics.txt @@ -24,10 +24,15 @@ func _[P ~[]int]() { _ = P{} } +var _ = I.m + -- @mToM/a.go -- @@ -5 +5 @@ -func (I) m() {} //@rename("m", "M", mToM) +func (I) M() {} //@rename("m", "M", mToM) +@@ -11 +11 @@ +-var _ = I.m ++var _ = I.M -- g.go -- package a diff --git a/gopls/internal/test/marker/testdata/rename/prepare.txt b/gopls/internal/test/marker/testdata/rename/prepare.txt index 7ac9581898e..2542648bc4b 100644 --- a/gopls/internal/test/marker/testdata/rename/prepare.txt +++ b/gopls/internal/test/marker/testdata/rename/prepare.txt @@ -33,6 +33,8 @@ func (*Y) Bobby() {} -- good/good0.go -- package good +var _ = stuff + func stuff() { //@item(good_stuff, "stuff", "func()", "func"),preparerename("stu", "stuff", span="stuff") x := 5 random2(x) //@preparerename("dom", "random2", span="random2") @@ -45,6 +47,8 @@ import ( "golang.org/lsptests/types" //@item(types_import, "types", "\"golang.org/lsptests/types\"", "package") ) +var _ = random + func random() int { //@item(good_random, "random", "func() int", "func") _ = "random() int" //@preparerename("random", "") y := 6 + 7 //@preparerename("7", "") diff --git a/gopls/internal/test/marker/testdata/symbol/basic.txt b/gopls/internal/test/marker/testdata/symbol/basic.txt index 12819ac188f..d993dd2ad60 100644 --- a/gopls/internal/test/marker/testdata/symbol/basic.txt +++ b/gopls/internal/test/marker/testdata/symbol/basic.txt @@ -76,6 +76,8 @@ func Dunk() int { return 0 } func dunk() {} +var _ = dunk + -- @want -- (*Quux).Do "func()" (Foo).Baz "func() string" +2 lines diff --git a/gopls/internal/test/marker/testdata/workspacesymbol/casesensitive.txt b/gopls/internal/test/marker/testdata/workspacesymbol/casesensitive.txt index 725e9dbb52d..e170aef87f1 100644 --- a/gopls/internal/test/marker/testdata/workspacesymbol/casesensitive.txt +++ b/gopls/internal/test/marker/testdata/workspacesymbol/casesensitive.txt @@ -77,6 +77,8 @@ func Dunk() int { return 0 } func dunk() {} +var _ = dunk + -- p/p.go -- package p diff --git a/gopls/internal/test/marker/testdata/workspacesymbol/issue44806.txt b/gopls/internal/test/marker/testdata/workspacesymbol/issue44806.txt index b2cd0b5c5a2..b88a1512df7 100644 --- a/gopls/internal/test/marker/testdata/workspacesymbol/issue44806.txt +++ b/gopls/internal/test/marker/testdata/workspacesymbol/issue44806.txt @@ -7,21 +7,21 @@ go 1.18 -- symbol.go -- package symbol -//@workspacesymbol("m", m) +//@workspacesymbol("M", M) type T struct{} // We should accept all valid receiver syntax when scanning symbols. -func (*(T)) m1() {} -func (*T) m2() {} -func (T) m3() {} -func ((T)) m4() {} -func ((*T)) m5() {} +func (*(T)) M1() {} +func (*T) M2() {} +func (T) M3() {} +func ((T)) M4() {} +func ((*T)) M5() {} --- @m -- -symbol.go:8:13-15 T.m1 Method -symbol.go:9:11-13 T.m2 Method -symbol.go:10:10-12 T.m3 Method -symbol.go:11:12-14 T.m4 Method -symbol.go:12:13-15 T.m5 Method +-- @M -- +symbol.go:8:13-15 T.M1 Method +symbol.go:9:11-13 T.M2 Method +symbol.go:10:10-12 T.M3 Method +symbol.go:11:12-14 T.M4 Method +symbol.go:12:13-15 T.M5 Method symbol.go:5:6-7 symbol.T Struct From 39e1a8c5aa1c7e285f0c8be5e5d5bd0eb1fd5fb0 Mon Sep 17 00:00:00 2001 From: Shulhan Date: Tue, 31 Dec 2024 07:10:05 +0700 Subject: [PATCH 69/73] godoc: fix missing (Added in Go) "x.xx" for function with type parameters For HTML documentation using godoc, since the introduction of type parameters, the new API for function with type parameters has not been rendered recently (no 1.XX displayed on the right). This changes fix it by checking for "[" first in the function name before "(". Change-Id: I9421e095bbce7ffe5f871441160d6cc87cc3f299 Reviewed-on: https://go-review.googlesource.com/c/tools/+/639475 Reviewed-by: Ian Lance Taylor Reviewed-by: Dmitri Shuralyov Auto-Submit: Ian Lance Taylor LUCI-TryBot-Result: Go LUCI --- godoc/versions.go | 2 +- godoc/versions_test.go | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/godoc/versions.go b/godoc/versions.go index 849f4d6470c..5a4dec33ea1 100644 --- a/godoc/versions.go +++ b/godoc/versions.go @@ -189,7 +189,7 @@ func parseRow(s string) (vr versionedRow, ok bool) { case strings.HasPrefix(rest, "func "): vr.kind = "func" rest = rest[len("func "):] - if i := strings.IndexByte(rest, '('); i != -1 { + if i := strings.IndexAny(rest, "[("); i != -1 { vr.name = rest[:i] return vr, true } diff --git a/godoc/versions_test.go b/godoc/versions_test.go index 0c5ca50c774..a021616ba11 100644 --- a/godoc/versions_test.go +++ b/godoc/versions_test.go @@ -65,6 +65,27 @@ func TestParseVersionRow(t *testing.T) { recv: "Encoding", }, }, + { + // Function with type parameters. + // Taken from "go/src/api/go1.21.txt". + row: "pkg cmp, func Compare[$0 Ordered]($0, $0) int #59488", + want: versionedRow{ + pkg: "cmp", + kind: "func", + name: "Compare", + }, + }, + { + // Function without type parameter but have "[" after + // "(" should have works as is. + // Taken from "go/src/api/go1.21.txt". + row: "pkg bytes, func ContainsFunc([]uint8, func(int32) bool) bool #54386", + want: versionedRow{ + pkg: "bytes", + kind: "func", + name: "ContainsFunc", + }, + }, } for i, tt := range tests { From ac39815d6464138ee84e7cd765bc43dbe1a3f4a7 Mon Sep 17 00:00:00 2001 From: linmaolin Date: Fri, 3 Jan 2025 00:27:40 +0000 Subject: [PATCH 70/73] go/packages: add GOROOT env to avoid TestTarget failure in OpenBSD When running tests in OpenBSD if 'go' is built with -trimpath, the TestTarget will always fail. Because when invoked without proper environments, 'go' itself fails to find the GOROOT path. Fixes golang/go#70891 Change-Id: I829b77686ae24d869653365f3e44e457c76b46bb GitHub-Last-Rev: 6e1311bbf08ead40dfefbe84a11d5888deac784b GitHub-Pull-Request: golang/tools#552 Reviewed-on: https://go-review.googlesource.com/c/tools/+/637961 Reviewed-by: Dmitri Shuralyov Reviewed-by: Michael Matloob LUCI-TryBot-Result: Go LUCI --- go/packages/packages_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go/packages/packages_test.go b/go/packages/packages_test.go index 1f2c681c481..fc420321c31 100644 --- a/go/packages/packages_test.go +++ b/go/packages/packages_test.go @@ -3339,7 +3339,7 @@ func main() { pkgs, err := packages.Load(&packages.Config{ Mode: packages.NeedName | packages.NeedTarget, - Env: []string{"GOPATH=" + gopath, "GO111MODULE=off"}, + Env: append(os.Environ(), "GOPATH=" + gopath, "GO111MODULE=off"), }, filepath.Join(gopath, "src", "...")) if err != nil { t.Fatal(err) From 29d66ee53ead1ec70aa4b9bfee5aea2ec18f0a6e Mon Sep 17 00:00:00 2001 From: Alan Donovan Date: Fri, 3 Jan 2025 15:08:03 -0500 Subject: [PATCH 71/73] gopls: update toolchain to go1.23.4 This fixes a serious miscompilation of range-over-func (see golang/go#70035, and the symptom at CL 640035). Change-Id: I843785a276c8181c5d6b799506d2808a5e7626ae Reviewed-on: https://go-review.googlesource.com/c/tools/+/640036 Auto-Submit: Alan Donovan LUCI-TryBot-Result: Go LUCI Reviewed-by: Robert Findley --- gopls/go.mod | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gopls/go.mod b/gopls/go.mod index 4c04f38a726..fdf9f7b7c92 100644 --- a/gopls/go.mod +++ b/gopls/go.mod @@ -1,8 +1,8 @@ module golang.org/x/tools/gopls -// go 1.23.1 fixes some bugs in go/types Alias support. -// (golang/go#68894 and golang/go#68905). -go 1.23.1 +// go 1.23.1 fixes some bugs in go/types Alias support (golang/go#68894, golang/go#68905). +// go 1.23.4 fixes a miscompilation of range-over-func (golang/go#70035). +go 1.23.4 require ( github.com/google/go-cmp v0.6.0 From 43ba4651fe4ce4a4e2ab9d6d60956db78c119d26 Mon Sep 17 00:00:00 2001 From: Alan Donovan Date: Fri, 3 Jan 2025 15:16:23 -0500 Subject: [PATCH 72/73] internal/analysis/modernize: minmax: reject IfStmt with Init Updates golang/go#71111 Change-Id: I86cdf1731e6057c4c972b91c46e8d3216a18382d Reviewed-on: https://go-review.googlesource.com/c/tools/+/640037 Auto-Submit: Alan Donovan LUCI-TryBot-Result: Go LUCI Reviewed-by: Robert Findley Commit-Queue: Alan Donovan --- gopls/internal/analysis/modernize/minmax.go | 9 +++++---- .../analysis/modernize/testdata/src/minmax/minmax.go | 8 ++++++++ .../modernize/testdata/src/minmax/minmax.go.golden | 8 ++++++++ 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/gopls/internal/analysis/modernize/minmax.go b/gopls/internal/analysis/modernize/minmax.go index 676090073f2..06330657876 100644 --- a/gopls/internal/analysis/modernize/minmax.go +++ b/gopls/internal/analysis/modernize/minmax.go @@ -84,11 +84,11 @@ func minmax(pass *analysis.Pass) { // Replace IfStmt with lhs = min(a, b). Pos: ifStmt.Pos(), End: ifStmt.End(), - NewText: []byte(fmt.Sprintf("%s = %s(%s, %s)", + NewText: fmt.Appendf(nil, "%s = %s(%s, %s)", formatNode(pass.Fset, lhs), sym, formatNode(pass.Fset, a), - formatNode(pass.Fset, b))), + formatNode(pass.Fset, b)), }}, }}, }) @@ -133,10 +133,10 @@ func minmax(pass *analysis.Pass) { // Replace rhs2 and IfStmt with min(a, b) Pos: rhs2.Pos(), End: ifStmt.End(), - NewText: []byte(fmt.Sprintf("%s(%s, %s)", + NewText: fmt.Appendf(nil, "%s(%s, %s)", sym, formatNode(pass.Fset, a), - formatNode(pass.Fset, b))), + formatNode(pass.Fset, b)), }}, }}, }) @@ -151,6 +151,7 @@ func minmax(pass *analysis.Pass) { ifStmt := curIfStmt.Node().(*ast.IfStmt) if compare, ok := ifStmt.Cond.(*ast.BinaryExpr); ok && + ifStmt.Init == nil && isInequality(compare.Op) != 0 && isAssignBlock(ifStmt.Body) { diff --git a/gopls/internal/analysis/modernize/testdata/src/minmax/minmax.go b/gopls/internal/analysis/modernize/testdata/src/minmax/minmax.go index d2efeb71589..393b3729e07 100644 --- a/gopls/internal/analysis/modernize/testdata/src/minmax/minmax.go +++ b/gopls/internal/analysis/modernize/testdata/src/minmax/minmax.go @@ -63,3 +63,11 @@ func shadowed() int { } return time } + +func nopeIfStmtHasInitStmt() { + x := 1 + if y := 2; y < x { // silent: IfStmt has an Init stmt + x = y + } + print(x) +} diff --git a/gopls/internal/analysis/modernize/testdata/src/minmax/minmax.go.golden b/gopls/internal/analysis/modernize/testdata/src/minmax/minmax.go.golden index c29d42d69a0..aacf84dd1c4 100644 --- a/gopls/internal/analysis/modernize/testdata/src/minmax/minmax.go.golden +++ b/gopls/internal/analysis/modernize/testdata/src/minmax/minmax.go.golden @@ -43,3 +43,11 @@ func shadowed() int { } return time } + +func nopeIfStmtHasInitStmt() { + x := 1 + if y := 2; y < x { // silent: IfStmt has an Init stmt + x = y + } + print(x) +} From 1743d1a7ef4ad2eb78666f099522a07b02bca878 Mon Sep 17 00:00:00 2001 From: Gopher Robot Date: Mon, 6 Jan 2025 08:48:07 -0800 Subject: [PATCH 73/73] go.mod: update golang.org/x dependencies Update golang.org/x dependencies to their latest tagged versions. Change-Id: I7d768b351bf53ce16029253b42252633b94fe8bb Reviewed-on: https://go-review.googlesource.com/c/tools/+/640716 LUCI-TryBot-Result: Go LUCI Auto-Submit: Gopher Robot Reviewed-by: Dmitri Shuralyov Reviewed-by: David Chase --- go.mod | 4 ++-- go.sum | 8 ++++---- gopls/go.mod | 2 +- gopls/go.sum | 10 +++++----- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index d7b6f18ddc1..0f49047782e 100644 --- a/go.mod +++ b/go.mod @@ -6,9 +6,9 @@ require ( github.com/google/go-cmp v0.6.0 github.com/yuin/goldmark v1.4.13 golang.org/x/mod v0.22.0 - golang.org/x/net v0.32.0 + golang.org/x/net v0.34.0 golang.org/x/sync v0.10.0 golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457 ) -require golang.org/x/sys v0.28.0 // indirect +require golang.org/x/sys v0.29.0 // indirect diff --git a/go.sum b/go.sum index 9b25a309b97..c788c5fbdc3 100644 --- a/go.sum +++ b/go.sum @@ -4,11 +4,11 @@ github.com/yuin/goldmark v1.4.13 h1:fVcFKWvrslecOb/tg+Cc05dkeYx540o0FuFt3nUVDoE= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= -golang.org/x/net v0.32.0 h1:ZqPmj8Kzc+Y6e0+skZsuACbx+wzMgo5MQsJh9Qd6aYI= -golang.org/x/net v0.32.0/go.mod h1:CwU0IoeOlnQQWJ6ioyFrfRuomB8GKF6KbYXZVyeXNfs= +golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= +golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= -golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457 h1:zf5N6UOrA487eEFacMePxjXAJctxKmyjKUsjA11Uzuk= golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457/go.mod h1:pRgIJT+bRLFKnoM1ldnzKoxTIn14Yxz928LQRYYgIN0= diff --git a/gopls/go.mod b/gopls/go.mod index fdf9f7b7c92..173614714cc 100644 --- a/gopls/go.mod +++ b/gopls/go.mod @@ -9,7 +9,7 @@ require ( github.com/jba/templatecheck v0.7.1 golang.org/x/mod v0.22.0 golang.org/x/sync v0.10.0 - golang.org/x/sys v0.28.0 + golang.org/x/sys v0.29.0 golang.org/x/telemetry v0.0.0-20241220003058-cc96b6e0d3d9 golang.org/x/text v0.21.0 golang.org/x/tools v0.28.0 diff --git a/gopls/go.sum b/gopls/go.sum index 69eabccc3f1..bba08403559 100644 --- a/gopls/go.sum +++ b/gopls/go.sum @@ -16,7 +16,7 @@ github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= -golang.org/x/crypto v0.30.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= +golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= golang.org/x/exp/typeparams v0.0.0-20241210194714-1829a127f884 h1:1xaZTydL5Gsg78QharTwKfA9FY9CZ1VQj6D/AZEvHR0= golang.org/x/exp/typeparams v0.0.0-20241210194714-1829a127f884/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= @@ -25,7 +25,7 @@ golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= -golang.org/x/net v0.32.0/go.mod h1:CwU0IoeOlnQQWJ6ioyFrfRuomB8GKF6KbYXZVyeXNfs= +golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= @@ -33,14 +33,14 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= -golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457/go.mod h1:pRgIJT+bRLFKnoM1ldnzKoxTIn14Yxz928LQRYYgIN0= golang.org/x/telemetry v0.0.0-20241220003058-cc96b6e0d3d9 h1:L2k9GUV2TpQKVRGMjN94qfUMgUwOFimSQ6gipyJIjKw= golang.org/x/telemetry v0.0.0-20241220003058-cc96b6e0d3d9/go.mod h1:8h4Hgq+jcTvCDv2+i7NrfWwpYHcESleo2nGHxLbFLJ4= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= -golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= +golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=