Gopls:診斷

Gopls 會不斷地為所有開啟的原始碼檔案提供各種診斷資訊。每次編輯檔案或更改配置時,gopls 都會非同步重新計算這些診斷資訊,並透過 LSP publishDiagnostics 通知傳送給客戶端,從而提供即時反饋,減少常見錯誤的成本。

診斷資訊主要來自兩個來源:編譯錯誤和分析發現。

  • 編譯錯誤 是您執行 go build 時會遇到的錯誤。Gopls 實際上並不執行編譯器;這會太慢。相反,它會在(需要時)執行 go list 來計算編譯元資料,然後以類似於編譯器前端的方式處理這些包:讀取、掃描和解析原始檔,然後進行型別檢查。這些步驟中的每一步都可能產生 gopls 會作為診斷資訊顯示的錯誤。

    LSP Diagnostic 記錄的 source 欄位指出了診斷資訊的來源:值為 "go list" 的診斷資訊來自 go list 命令,值為 "compiler" 的診斷資訊來自 gopls 的解析或型別檢查階段,這些階段與 Go 編譯器使用的階段類似。

    A diagnostic due to a type error

    上面的示例顯示了 string + int 的加法,導致型別檢查器報告 MismatchedTypes 錯誤。該診斷資訊包含指向有關此類型別錯誤的文件的連結。

  • 分析發現 來自 Go 分析框架,該框架是 go vet 用於對 Go 程式碼應用各種附加靜態檢查的系統。最廣為人知的例子是 printf 分析器,它會報告 fmt.Printf 的呼叫,其中格式“動詞”與引數不匹配,例如 fmt.Printf("%d", "three")

    Gopls 提供了數十種來自各種套件的分析器;完整的列表請參閱 分析器。由分析器生成的每個診斷資訊的 source 欄位會記錄生成它的分析器的名稱。

    A diagnostic due to an analysis finding

    上面的示例顯示了一個 printf 格式化錯誤。該診斷資訊包含指向 printf 分析器文件的連結。

還有一個可選的第三類診斷資訊來源

  • 編譯器最佳化詳細資訊 是報告與 Go 編譯器最佳化決策相關的詳細資訊的診斷資訊,例如變數是否會逃逸或切片索引是否需要邊界檢查。

    最佳化決策包括:變數是否逃逸,以及如何推斷逃逸;是否隱含了 nil 指標檢查或消除了 nil 指標檢查;以及函式是否可以內聯。

    此來源預設停用,但可以透過呼叫 source.toggleCompilerOptDetails (“{顯示,隱藏} 編譯器最佳化詳細資訊”) 程式碼操作按包啟用。

    請記住,編譯器最佳化器僅在沒有編譯錯誤的包上執行,因此最佳化診斷資訊不會顯示在無法構建的包上。

診斷資訊的重新計算

預設情況下,每次修改原始檔時都會自動重新計算診斷資訊。

開啟檔案中的編譯錯誤會在每次檔案更改後(可能在每次按鍵後)很短的延遲(幾十毫秒)後更新。這確保了在編輯時能快速反饋語法和型別錯誤。

整個工作區的編譯和分析診斷資訊的計算成本很高,因此它們通常在編輯後短暫空閒一段時間(約 1 秒)後重新計算。

diagnosticsDelay 設定決定了這個時間段。或者,可以使用 diagnosticsTrigger 設定,僅在儲存已編輯的檔案後觸發診斷資訊。

當使用 "pullDiagnostics": true 初始化時,gopls 還支援 “拉取診斷”,這是一種替代的重新計算診斷資訊機制,在這種機制中,客戶端使用 textDocument/diagnostic 請求顯式地從 gopls 請求診斷資訊。此功能預設關閉,直到拉取診斷的效能與推送診斷相當。

快速修復

每個分析器診斷資訊都可以透過編輯程式碼提供一種或多種替代的修復方法。例如,當 return 語句的返回值數量不足時,fillreturns 分析器會建議一種修復方法,該方法會啟發式地用合適的值填充缺失的返回值。應用修復即可消除編譯錯誤。

An analyzer diagnostic with two alternative fixes

上面的截圖顯示了 VS Code 對“未使用引數”分析診斷資訊的快速修復選單,其中包含兩個替代修復選項。(有關詳細資訊,請參閱 刪除未使用引數。)

明確安全的建議修復是 "source.fixAll" 型別的 程式碼操作。許多客戶端編輯器都有一個快捷鍵來應用所有此類修復。

TODO(adonovan):稽核所有分析器,確保其文件與它們建議的任何修復措施保持同步。

設定

  • diagnosticsDelay 設定決定了編輯後診斷資訊重新計算前的空閒時間。
  • diagnosticsTriggerr 設定決定了哪些事件會觸發診斷資訊的重新計算。
  • linkTarget 設定指定了 Diagnostic.CodeDescription 欄位中 Go 包連結的基礎 URI。

客戶端支援

  • VS Code:每個診斷資訊會顯示為波浪線。懸停滑鼠會顯示詳細資訊以及任何建議的修復。
  • Emacs + eglot:每個診斷資訊會顯示為波浪線。懸停滑鼠會顯示詳細資訊。使用 M-x eglot-code-action-quickfix 應用可用的修復;如果有多個修復,它會提示您選擇。
  • Vim + coc.nvim: ??
  • CLIgopls check file.go

stubMissingInterfaceMethods:宣告介面缺失的方法

當具體型別的值被賦給介面型別變數,但具體型別缺少所有必需的方法時,型別檢查器將報告“缺失方法”錯誤。

在這種情況下,gopls 提供了一個快速修復,用於向具體型別新增所有缺失方法的存根宣告,使其實現介面。

例如,此函式將無法編譯,因為值 NegativeErr{} 沒有實現“error”介面

func sqrt(x float64) (float64, error) {
    if x < 0 {
        return 0, NegativeErr{} // error: missing method
    }
    ...
}

type NegativeErr struct{}

Gopls 將提供一個快速修復來宣告此方法


// Error implements error.Error.
func (NegativeErr) Error() string {
    panic("unimplemented")
}

請注意,新的宣告會出現在具體型別旁邊,這可能與游標位置在不同的檔案甚至不同的包。(也許 gopls 應該傳送一個 showDocument 請求導航客戶端到那裡,或者傳送一個進度通知來指示發生了什麼。)

StubMissingCalledFunction:宣告缺失的方法 T.f

當您嘗試在沒有該方法的型別上呼叫方法時,編譯器將報告類似“type X has no field or method Y”的錯誤。在這種情況下,gopls 現在提供一個快速修復,用於生成缺失方法的存根宣告,並根據呼叫推斷其型別。

考慮以下程式碼,其中 Foo 沒有 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 將提供一個快速修復,“宣告缺失的方法 Foo.bar”。呼叫時,它會建立以下宣告

func (f Foo) bar(s string, i int) string {
    panic("unimplemented")
}

CreateUndeclared:建立“未宣告名稱:X”的缺失宣告

Go 編譯器錯誤“未宣告名稱:X”表示變數或函式在使用前未在當前作用域中宣告。在這種情況下,gopls 提供了一個快速修復來建立宣告。

宣告新變數

當您引用尚未宣告的變數時

func main() {
  x := 42
  min(x, y) // error: undefined: y
}

快速修復將根據上下文推斷其型別並插入一個預設值的宣告

func main() {
  x := 42
  y := 0
  min(x, y)
}

宣告新函式

同樣,如果您呼叫了一個尚未宣告的函式

func main() {
  var s string
  s = doSomething(42) // error: undefined: doSomething
}

Gopls 將在下方插入一個新的函式宣告,根據呼叫推斷其型別

func main() {
  var s string
  s = doSomething(42)
}

func doSomething(i int) string {
  panic("unimplemented")
}

本文件的原始碼可以在 golang.org/x/tools/gopls/doc 下找到。