はじめに
コード以外の情報を元にして自動生成したことはあったのですが、コードを静的解析した内容をもとに自動生成するのは初めてだったので、調べた内容をまとめる記事を書きました。
今回は、特定のパッケージ配下にある構造体全てにメソッドを生やすコードを生成しようと思います。
※typeで型定義した構造体のみ生成対象とします
また、今回説明する内容を実装したサンプルリポジトリも作成しましたので、必要であればそちらも参照してください。
コードを解析
go/typesを使用して型情報を取得
goは標準でコードを静的解析するためのライブラリが整えられていますが、その中でもgo/types
は、型情報を取得するのに使えます。
以下は、パッケージ配下に存在する、typeで型定義した構造体の名前の一覧を取得する処理です
structNames := []string{}
// 生成方法は後述
var pkg *types.Package
// パッケージ配下に存在する各要素の名前を取得
for _, name := range pkg.Scope().Names() {
// typeで定義しているかどうかチェック
obj, ok := pkg.Scope().Lookup(name).(*types.TypeName)
if !ok {
continue
}
// structかどうかチェック
if _, ok := obj.Type().Underlying().(*types.Struct); !ok {
continue
}
structNames = append(structNames, obj.Name())
}
*types.Packageの生成
*types.Package
を使用して型情報を取得できるとわかったところで、肝心の*types.Package
の生成方法ですが、パッケージ解析を実施するためのライブラリであるx/tools/go/packagesから取得することができるので、こちら経由で取得します。
なお、同様の機能を持つライブラリとしてgolang.org/x/tools/go/loaderがありますが、こちらはモジュールをサポートしていないため非推奨であり、代わりにx/tools/go/packages
を使用するよう促されています。
例) bytesパッケージの型情報を取得する
cfg := &packages.Config{
// 型情報を取得するモードを指定する。必要であれば他のモードも追加して情報を取得可能
Mode: packages.NeedTypes | packages.NeedTypesInfo,
}
// パッケージ情報をロード
pPkgs, _ := packages.Load(cfg, "bytes")
for _, pPkg := range pPkgs {
var pkg *types.Package = pPkg.Types
// 以降型情報の解析
}
これで*types.Package
を取得できるので、後は前述したとおり型情報を解析していけばOKです。
今回は例としてbytes
パッケージを直接指定しましたが、他にも様々な指定方法があるようです。
コードを生成
コードを解析して必要な情報が得られたら、コード生成です。
今回はテキストテンプレートを使用してコードを生成します。
func main() {
packageName := "pkg"
structNames := []string{"A", "B"}
templData := struct {
PackageName string
StructNames []string
}{
PackageName: packageName,
StructNames: structNames,
}
var w bytes.Buffer
tmpl := template.Must(template.New("mytemplate").Parse(templStr))
tmpl.Execute(&w, templData)
// 以下、ファイル出力など...
}
const templStr = `// Code generated by gen/genmethods.go; DO NOT EDIT.
package {{ .PackageName }}
import "fmt"
{{ range $structName := .StructNames }}
// PrintType 型情報を標準出力する
func (s {{ $structName }}) PrintType() {
fmt.Printf("%T\n", s)
}
{{ end }}
`
実行したら以下のようなコードを生成できます。
// Code generated by gen/genmethods.go; DO NOT EDIT.
package pkg
import "fmt"
// PrintType 型情報を標準出力する
func (s A) PrintType() {
fmt.Printf("%T\n", s)
}
// PrintType 型情報を標準出力する
func (s B) PrintType() {
fmt.Printf("%T\n", s)
}
ソースコードのフォーマット(format.Source)やパッケージのimportを実施する処理(imports.Process)も用意されているので、必要であれば使いましょう。(サンプルリポジトリでは両方使っています)
リンク集
- go/types関連
- /tools/go/packages関連
- コード生成関連