Gopls: 診断

Gopls は、開いているすべてのソースコードファイルを様々な診断で継続的に注釈付けします。ファイルを編集したり、設定を変更したりするたびに、gopls はこれらの診断を非同期で再計算し、LSP publishDiagnostics 通知を使用してクライアントに送信し、一般的な間違いのコストを削減するリアルタイムのフィードバックを提供します。

診断は、主にコンパイルエラーと分析結果の2つのソースから提供されます。

  • コンパイルエラーは、go buildを実行したときに発生するエラーです。Gopls は実際にコンパイラを実行しません。それでは遅すぎます。代わりに、必要に応じてgo listを実行してコンパイルのメタデータを計算し、Goコンパイラのフロントエンドと同様の方法でこれらのパッケージを処理します。ソースファイルの読み込み、スキャン、解析、型チェックを行います。これらの各ステップで、gopls が診断として表示するエラーが発生する可能性があります。

    LSP Diagnostic レコードの source フィールドは、診断の発生源を示します。"go list"のソースを持つものはgo listコマンドから、"compiler"のソースを持つものは、Goコンパイラで使用されるものと同様のgoplsの解析または型チェックフェーズから発生します。

    A diagnostic due to a type error

    上記の例では、string + intの加算が、型チェッカーにMismatchedTypesエラーを報告させています。診断には、この種の型エラーに関するドキュメントへのリンクが含まれています。

  • 分析結果は、Go分析フレームワーク、つまりgo vetがGoコードに様々な追加の静的チェックを適用するために使用するシステムから得られます。最もよく知られている例は、printfアナライザで、fmt.Printf("%d", "three")のように、フォーマット「動詞」が引数と一致しないfmt.Printfの呼び出しを報告します。

    Gopls は、様々なスイートから集約された数十のアナライザを提供しています。完全なリストについては、「アナライザ」を参照してください。アナライザによって生成された各診断のsourceフィールドには、それを生成したアナライザの名前が記録されます。

    A diagnostic due to an analysis finding

    上記の例は、printfの書式設定の誤りを示しています。診断には、printfアナライザのドキュメントへのリンクが含まれています。

診断にはオプションで3番目のソースがあります

  • コンパイラの最適化の詳細は、変数がエスケープするか、スライスインデックスが境界チェックを必要とするかなど、Goコンパイラが行う最適化の決定に関連する詳細を報告する診断です。

    最適化の決定には、変数がエスケープするか、エスケープがどのように推論されるか、nilポインタチェックが暗黙的または削除されるか、関数がインライン化できるかなどが含まれます。

    このソースはデフォルトでは無効になっていますが、source.toggleCompilerOptDetails ("{Show,Hide} compiler optimization details") コードアクションを呼び出すことで、パッケージごとに有効にすることができます。

    コンパイラのオプティマイザは、エラーのないパッケージに対してのみ実行されるため、ビルドできないパッケージには最適化診断は表示されません。

診断の再計算

デフォルトでは、ソースファイルが編集されるたびに診断が自動的に再計算されます。

開いているファイルのコンパイルエラーは、ファイルが変更されるたびに、場合によってはキーストロークごとに、非常に短い遅延(数十ミリ秒)の後に更新されます。これにより、編集中の構文エラーや型エラーの迅速なフィードバックが保証されます。

ワークスペース全体のコンパイルおよび分析診断は計算コストが高いため、通常は編集後、短いアイドル期間(約1秒)の後に再計算されます。

diagnosticsDelay 設定はこの期間を決定します。あるいは、diagnosticsTrigger 設定を使用して、編集されたファイルが保存された後にのみ診断をトリガーすることもできます。

"pullDiagnostics": trueで初期化すると、gopls は「プル診断」もサポートします。これは、クライアントがtextDocument/diagnosticリクエストを使用してgoplsに診断を明示的に要求する、診断を再計算するための代替メカニズムです。この機能は、プル診断のパフォーマンスがプッシュ診断と同等になるまで、デフォルトではオフになっています。

クイックフィックス

各アナライザ診断は、コードを編集することで問題を修正する1つ以上の代替方法を提案することがあります。たとえば、returnステートメントのオペランドが少なすぎる場合、fillreturnsアナライザは、不足しているオペランドを適切な値で経験的に埋める修正を提案します。修正を適用すると、コンパイルエラーが解消されます。

An analyzer diagnostic with two alternative fixes

上記のスクリーンショットは、「未使用のパラメータ」分析診断に対するVS Codeのクイックフィックスメニューで、2つの代替修正案を示しています。(詳細については、「未使用のパラメータを削除する」を参照してください。)

疑いなく安全な提案された修正は、種類が"source.fixAll"であるコードアクションです。多くのクライアントエディタには、そのような修正をすべて適用するためのショートカットがあります。

TODO(adonovan): 提案されるすべての修正に関して、アナライザのドキュメントが最新であることを確認するためにすべてのアナライザを監査する。

設定

  • diagnosticsDelay 設定は、診断が再計算されるまでの編集後のアイドル期間を決定します。
  • diagnosticsTriggerr 設定は、診断の再計算を引き起こすイベントを決定します。
  • linkTarget 設定は、Diagnostic.CodeDescriptionフィールド内のGoパッケージリンクのベースURIを指定します。

クライアントサポート

  • VS Code: 各診断は波線で表示されます。ホバーすると、詳細と提案された修正が表示されます。
  • Emacs + eglot: 各診断は波線で表示されます。ホバーすると詳細が表示されます。利用可能な修正を適用するにはM-x eglot-code-action-quickfixを使用します。複数の修正がある場合はプロンプトが表示されます。
  • Vim + coc.nvim: ??
  • CLI: gopls check file.go

stubMissingInterfaceMethods: Iの不足しているメソッドを宣言する

具象型の値をインターフェース型の変数に割り当てたときに、具象型がすべての必要なメソッドを持っていない場合、型チェッカーは「メソッドが見つかりません」というエラーを報告します。

このような状況で、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 はクイックフィックス「Declare missing method Foo.bar」を提供します。これを呼び出すと、次の宣言が作成されます。

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

CreateUndeclared: 「宣言されていない名前: X」の不足している宣言を作成する

Goコンパイラのエラー「undeclared name: 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 の下にあります。