Go 1.10 Release Party in Tokyo - connpass
「Go 1.10 ツール周辺の CL を読む」という話をしました。
Go 1.10 ツール周辺の CL を読む - Google スライド
Songmu さんに「モテメン、Goリリースパーティー出ない?」と言われて安請け合いしたはいいものの、とくに話すネタがないのでどうしようかなー、と src/go
以下の変更を読んでいたら普通に面白かったので、そのへんの話をまとめてみました。最初は go test -json
の話をしようと考えていたんだけど、go vet
周辺の変更がよかったですね。
最後の方の 1 CL 1 枚のスライドは時間が余ったときのパディングで、発表のときはほぼスキップしました。以下、メインの go vet
の話をざっくりまとめておきます。
CL 74356 に至る道
今回の主眼は go test
中にシームレスに go vet
を行うようにする、という変更ですが、動機は以下のイシューに端的にまとめられています。
The key insight is that running 'go vet' is considered a best practice, but why is that something people should need to learn and think to do explicitly? If it's such a good practice, it should be integrated into something that happens already.
go vet
の実体は go tool vet
($GOTOOLDIR/vet
) ですが、これは go/*
系の標準パッケージによって構成されている一方で、Go ツールチェインのコンパイルには cmd/compile
以下の特製のソースが利用されている、かつこちらが正であるため、go/*
系、とくに go/types
のふるまいを cmd/compile
にあわせてやる必要があります(これまで go vet
は vendoring や cgo にうまく対応できていないという問題がありました)。
CL 74750: go tool vet
に JSON を渡す
そこで、cmd/compile
のおこなった import の解決結果をそのまま vet に渡す、という方法が採られました。これまでは .go ソースコードのリストを引数に取っていましたが、vet.cfg
という JSON ファイルを引数にあたえることで、vet の解析により詳細な指示を与えられるようになります。go vet -x
すると分かるんですが、こんな感じ。
{ "Compiler": "gc", "Dir": "/Users/motemen/dev/go/src/github.com/motemen/prchecklist", "GoFiles": [ "/Users/motemen/dev/go/src/github.com/motemen/prchecklist/context.go", "/Users/motemen/dev/go/src/github.com/motemen/prchecklist/models.go", "/Users/motemen/dev/go/src/github.com/motemen/prchecklist/version.go", "/Users/motemen/dev/go/src/github.com/motemen/prchecklist/context_test.go", "/Users/motemen/dev/go/src/github.com/motemen/prchecklist/models_test.go" ], "ImportMap": { "context": "context", "fmt": "fmt", "github.com/pkg/errors": "github.com/motemen/prchecklist/vendor/github.com/pkg/errors", "golang.org/x/oauth2": "github.com/motemen/prchecklist/vendor/golang.org/x/oauth2", "net/http": "net/http", "net/url": "net/url", "testing": "testing" }, "PackageFile": { "context": "/usr/local/Cellar/go/1.10/libexec/pkg/darwin_amd64/context.a", "fmt": "/usr/local/Cellar/go/1.10/libexec/pkg/darwin_amd64/fmt.a", "github.com/motemen/prchecklist/vendor/github.com/pkg/errors": "$WORK/b098/_pkg_.a", "github.com/motemen/prchecklist/vendor/golang.org/x/oauth2": "$WORK/b099/_pkg_.a", "net/http": "/usr/local/Cellar/go/1.10/libexec/pkg/darwin_amd64/net/http.a", "net/url": "/usr/local/Cellar/go/1.10/libexec/pkg/darwin_amd64/net/url.a", "testing": "/usr/local/Cellar/go/1.10/libexec/pkg/darwin_amd64/testing.a" }, "ImportPath": "github.com/motemen/prchecklist", "SucceedOnTypecheckFailure": false }
- GoFiles は解析対象のファイル、
- ImportMap は
import
文に指定されたパッケージパスから実際に解決されたパスへのマップ(.../vendor/...
なものが存在してるのが見て取れると思います)、 - PackageFile は解決されたパッケージパスからビルドされたオブジェクトファイルのパスへのマップ
というふうになってます。go tool vet
のほうではこの情報をもとに、ソースコードの型情報の解析の際、import
されたパッケージの型情報を解決します。
ちなみに SucceedOnTypecheckFailure は go test
時に true となり、go/types
による型チェックが失敗しても go vet
を異常終了させないフラグです。go/types
にはコーナーケースで型チェックが失敗する既知のバグがあり、これのワークアラウンドになります。go test
は build → vet → test という順序で実行されるので vet が走るときにはすでに(cmd/compile
による)ビルドが完遂しており、vet による型チェックの失敗はユーザにとって偽の情報となるから無視するわけです。
CL 74354: go/importer.For が lookup 対応
さて、パッケージパスからオブジェクトファイルへの完全なマッピングが得られただけでは go/types
の型解析は完全にはなりません。なぜならそういったマッピングに(Go1.9 の段階では)対応していないからです。
go/types
では型チェックをおこなう際、import
されたパッケージの型情報を得るために go/importer
というパッケージを使用します。go/importer
は
func For(compiler string, lookup Lookup) types.Importer type Lookup func(path string) (io.ReadCloser, error)
という API を主な機能として提供していて、パッケージパスから型情報を返す types.Importer
を生成します。
type Importer interface { Import(path string) (*Package, error) }
lookup
はパッケージパスから「ファイル的なもの」を返す関数で、これによってモジュールのオブジェクトの場所をカスタマイズできるのですが、この引数に長らく nil しか許されていなかったのが、このたびサポートされることとなりました。
vet の中ではこれを利用し、コマンドライン引数で受けたマッピングをもとに、パッケージのオブジェクトファイルの場所を cmd/compile
由来のものとしています。
おまけ: cmd/compile vs. go/types
以上の話では cmd/compile
が go/types
の上位版であるという認識が背景にあったのですが、じつは go/types
のほうが型チェックに厳しいところがあって、例えば以下のようなソースコードは、go build
はできるものの go/types
のチェックを通りません。
https://play.golang.org/p/2BohDi9Mwr8
func f() { var x int // x declared but not used go func() { x = 42 }() }
今回の変更で go test
時に vet
が走るようになり、このような形のテストはみな失敗することになったわけですが、Go のテストコードはそれにあわせてこのような変数を _
に代入して、このエラーを回避するようになったのでした。おもしろいですね。
.@motemen 「はてなでリリースパーティやってたときは参加したことなかったです」 #go110party pic.twitter.com/ZD4TRXx8hO
— Yoshifumi Yamaguchi (@ymotongpoo) 2018年2月20日