Gopls continuously annotates all your open files of source code with a
variety of diagnostics. Every time you edit a file or make a
configuration change, gopls asynchronously recomputes these
diagnostics and sends them to the client using the LSP
publishDiagnostics
notification, giving you real-time feedback that reduces the cost of
common mistakes.
Diagnostics come from two main sources: compilation errors and analysis findings.
-
Compilation errors are those that you would obtain from running
go build
. Gopls doesn't actually run the compiler; that would be too slow. Instead it runsgo list
(when needed) to compute the metadata of the compilation, then processes those packages in a similar manner to the compiler front-end: reading, scanning, and parsing the source files, then type-checking them. Each of these steps can produce errors that gopls will surface as a diagnostic.The
source
field of the LSPDiagnostic
record indicates where the diagnostic came from: those with source"go list"
come from thego list
command, and those with source"compiler"
come from gopls' parsing or type checking phases, which are similar to those used in the Go compiler.The example above shows a
string + int
addition, causes the type checker to report aMismatchedTypes
error. The diagnostic contains a link to the documentation about this class of type error. -
Analysis findings come from the Go analysis framework, the system used by
go vet
to apply a variety of additional static checks to your Go code. The best-known example is theprintf
analyzer, which reports calls tofmt.Printf
where the format "verb" doesn't match the argument, such asfmt.Printf("%d", "three")
.Gopls provides dozens of analyzers aggregated from a variety of suites; see Analyzers for the complete list. The
source
field of each diagnostic produced by an analyzer records the name of the analyzer that produced it.The example above shows a
printf
formatting mistake. The diagnostic contains a link to the documentation for theprintf
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
("{Show,Hide} compiler optimization details") code action.Remember that the compiler's optimizer runs only on packages that are transitively free from errors, so optimization diagnostics will not be shown on packages that do not build.
By default, diagnostics are automatically recomputed each time the source files are edited.
Compilation errors in open files are updated after a very short delay (tens of milliseconds) after each file change, potentially after every keystroke. This ensures rapid feedback of syntax and type errors while editing.
Compilation and analysis diagnostics for the whole workspace are much more expensive to compute, so they are usually recomputed after a short idle period (around 1s) following an edit.
The diagnosticsDelay
setting determines
this period.
Alternatively, diagnostics may be triggered only after an edited file
is saved, using the
diagnosticsTrigger
setting.
When initialized with "pullDiagnostics": true
, gopls also supports
"pull diagnostics",
an alternative mechanism for recomputing diagnostics in which the client
requests diagnostics from gopls explicitly using the textDocument/diagnostic
request. This feature is off by default until the performance of pull
diagnostics is comparable to push diagnostics.
Each analyzer diagnostic may suggest one or more alternative
ways to fix the problem by editing the code.
For example, when a return
statement has too few operands,
the fillreturns
analyzer
suggests a fix that heuristically fills in the missing ones
with suitable values. Applying the fix eliminates the compilation error.
The screenshot above shows VS Code's Quick Fix menu for an "unused parameter" analysis diagnostic with two alternative fixes. (See Remove unused parameter for more detail.)
Suggested fixes that are indisputably safe are code
actions whose kind is
"source.fixAll"
.
Many client editors have a shortcut to apply all such fixes.
TODO(adonovan): audit all the analyzers to ensure that their documentation is up-to-date w.r.t. any fixes they suggest.
Settings:
- The
diagnosticsDelay
setting determines the idle period after an edit before diagnostics are recomputed. - The
diagnosticsTriggerr
setting determines what events cause recomputation of diagnostics. - The
linkTarget
setting specifies the base URI for Go package links in the Diagnostic.CodeDescription field.
Client support:
- VS Code: Each diagnostic appears as a squiggly underline. Hovering reveals the details, along with any suggested fixes.
- Emacs + eglot: Each diagnostic appears as a squiggly underline.
Hovering reveals the details. Use
M-x eglot-code-action-quickfix
to apply available fixes; it will prompt if there are more than one. - Vim + coc.nvim: ??
- CLI:
gopls check file.go
When a value of a concrete type is assigned to a variable of an interface type, but the concrete type does not possess all the necessary methods, the type checker will report a "missing method" error.
In this situation, gopls offers a quick fix to add stub declarations of all the missing methods to the concrete type so that it implements the interface.
For example, this function will not compile because the value
NegativeErr{}
does not implement the "error" interface:
func sqrt(x float64) (float64, error) {
if x < 0 {
return 0, NegativeErr{} // error: missing method
}
...
}
type NegativeErr struct{}
Gopls will offer a quick fix to declare this method:
// Error implements error.Error.
func (NegativeErr) Error() string {
panic("unimplemented")
}
Beware that the new declarations appear alongside the concrete type,
which may be in a different file or even package from the cursor
position.
(Perhaps gopls should send a showDocument
request to navigate the
client there, or a progress notification indicating that something
happened.)
When you attempt to call a method on a type that does not have that method, the compiler will report an error such as "type X has no field or method Y". In this scenario, gopls now offers a quick fix to generate a stub declaration of the missing method, inferring its type from the call.
Consider the following code where Foo
does not have a method bar
:
type Foo struct{}
func main() {
var s string
f := Foo{}
s = f.bar("str", 42) // error: f.bar undefined (type Foo has no field or method bar)
}
Gopls will offer a quick fix, "Declare missing method Foo.bar". When invoked, it creates the following declaration:
func (f Foo) bar(s string, i int) string {
panic("unimplemented")
}
A Go compiler error "undeclared name: X" indicates that a variable or function is being used before it has been declared in the current scope. In this scenario, gopls offers a quick fix to create the declaration.
When you reference a variable that hasn't been declared:
func main() {
x := 42
min(x, y) // error: undefined: y
}
The quick fix would insert a declaration with a default value inferring its type from the context:
func main() {
x := 42
y := 0
min(x, y)
}
Similarly, if you call a function that hasn't been declared:
func main() {
var s string
s = doSomething(42) // error: undefined: doSomething
}
Gopls will insert a new function declaration below, inferring its type from the call:
func main() {
var s string
s = doSomething(42)
}
func doSomething(i int) string {
panic("unimplemented")
}