go/analysis: publish x/tools/go/analysis types in the standard library
要約
概要
golang.org/x/tools/go/analysis パッケージの型を、新しい標準ライブラリパッケージ go/analysis として公開する提案です。このパッケージは、go vet などのドライバツールとアナライザ(リンター)をつなぐインターフェースを定めています。公開後、x/tools 側の型はすべて標準ライブラリの型の型エイリアス(同じ型を指す別名)になります。
ステータス変更
active → likely_accept
Proposal Review Meeting では、まず「標準ライブラリだけが抱えるベンダリングの技術的事情を除いても、正しい判断と言えるか」が問われました。答えは「Yes」です。本当の動機は go test にアナライザを組み込むこと(#82464)ですが、go/analysis はコミュニティでも標準ライブラリ内部でも広く使われており、標準ライブラリの go/ 配下に置くのが自然だと判断されました。さらに次の点も整理されました。
- このパッケージはインターフェースだけで、アナライザを実行するロジックは外部のドライバ側に残る
- 「インターフェースを標準化する」ことは標準ライブラリの役割の一つである
- 反対意見は「std を大きくしたくない」という漠然としたものしかなく、利点ははっきりしている
- std が外部モジュールに依存する状態はリリースエンジニアリング上とても扱いにくく、それが減る
以上から likely accept と判断されました。
技術的背景
現状の問題点
cmd モジュール(GOROOT/src/cmd)には、x/tools/go/analysis のコピーがベンダリング(依存コードをリポジトリ内に複製して取り込む方式)されています。#82464 では、go test が「ワークスペースで指定されたアナライザ」を実行できるようにすることを目指しています。そのためには go test が cmd/vet のようなツールをその場でビルドし、ユーザーのアナライザとリンクする必要があります。ところがユーザーのアナライザは本物の golang.org/x/tools/go/analysis の型で書かれています。vendor 内のコピーはパッケージパスが違うため Go の型システム上は別の型として扱われ、両者には互換性がありません。
提案された解決策
Validate 関数と CycleInRequiresGraphError を除くすべての型を go/analysis に移します。対象は以下のとおりです。
package analysis // "go/analysis"
type Analyzer struct{ ... }
type Diagnostic struct{ ... }
type Fact interface{ ... }
type Module struct{ ... }
type ModuleError struct{ ... }
type ObjectFact struct{ ... }
type PackageFact struct{ ... }
type Pass struct{ ... }
type Range interface{ ... }
type RelatedInformation struct{ ... }
type SuggestedFix struct{ ... }
type TextEdit struct{ ... }
// 付随するメソッド(後のコメントで明確化)
func (a *Analyzer) String() string
func (pass *Pass) ReportRangef(rng Range, format string, args ...any)
func (pass *Pass) Reportf(pos token.Pos, format string, args ...any)
func (pass *Pass) String() string
互換性を守るため、型の宣言は一切変えません。v2 として API を整理する機会にはしない、と明言されています。そのためレビューで問われたのは「移すか移さないか」だけです。実装は当初 GOEXPERIMENT=goanalysis の下で進められます。std の新パッケージも x/tools 側のエイリアスも、フラグを有効にしたときだけ存在するので、いつでも元に戻せます。
これによって何ができるようになるか
go testでカスタムアナライザを実行する(#82464): std、cmd、x/tools が同じ型を参照するので、ユーザー定義のアナライザをgo testが生成するツールにそのままリンクできます。- 既存アナライザはそのまま動く: x/tools の型がエイリアスになるだけなので、既存のアナライザやドライバ(staticcheck、golangci-lint など)はソースを変えずに動き続けます。
- 標準ライブラリだけでアナライザを書ける: インターフェースの型定義に限れば、外部モジュールに依存せずに済みます。
コード例
// Before: x/tools のパッケージを使う(cmd 内の vendor コピーとは別の型になる)
import "golang.org/x/tools/go/analysis"
var Analyzer = &analysis.Analyzer{
Name: "mycheck",
Doc: "reports misuse of MyAPI",
Run: run,
}
func run(pass *analysis.Pass) (any, error) {
pass.Reportf(pos, "do not call MyAPI here")
return nil, nil
}
// After: 標準ライブラリの go/analysis を使う
import "go/analysis"
var Analyzer = &analysis.Analyzer{ /* 同上 */ }
// x/tools 側は次のようなエイリアスになり、両者は同一の型になる
// package analysis // golang.org/x/tools/go/analysis
// type Analyzer = analysis.Analyzer // "go/analysis" の型
議論のハイライト
- v2 化しない理由: @willfaught は「std に移す前に x/tools で v2 として API を磨くべきでは」と問いかけました。しかし提案の前提は、長年安定してきた API をエイリアスで完全に互換に保つことです。宣言の変更はスコープ外とされています。
- エイリアスの後方互換性: @mateusz834 は、モジュールをまたぐエイリアス化は厳密には後方互換ではないと指摘しました。旧型と新型の両方を列挙する type switch がコンパイルエラーになる、
reflect.Type.String()の値が変わる、といった例です。@adonovan は、そうした壊れやすいコードはまれだと回答しました(このやり取りは off-topic 扱い)。 - std 肥大化への懸念: 標準ライブラリを小さく、あるいはモジュール化したいという方針はあります。それでも、x/ で実績を積んだものを std に移すのは理にかなうと整理されました。
- ドキュメント表示の課題: go doc はエイリアスの表示が貧弱です。@adonovan は、x/tools/go/analysis のドキュメント品質をできる限り保つと約束しました。