GoのコードからPlantUMLコードを生成する静的解析ツールを作っている
tl;dr
go/types
パッケージがめちゃくちゃ便利
gouml
の紹介
Goのコードを静的解析し、PlantUMLのコードを吐くパーサを開発している。
例えばGo言語で書かれたこのソースファイルを渡すと、
package main type Human struct { Name string Age Age } type Age int func (a Age) IsAdult() bool { return a >= 20 }
以下のような PlantUML コードが生成される。
package "main" { class "Age" as main.Age <<V,Orchid>> { +IsAdult(): bool } } package "main" { class "Human" as main.Human <<V,Orchid>> { +Name: string +Age: main.Age } } main.Human --> main.Age
これはUML図としてこのように描画される。
(描画はふつうに本家に任せた。)
インターフェイスの関連にも対応している。
type Animal interface { Cry() string } type Cat struct { Name string } func (c Cat) Cry() string { return "にゃんにゃん" } type Dog struct { Name string } func (d Dog) Cry() string { return "わんわん" }
Goのおさらい
Goは、型に type
演算子で名前付けることで新たに型を定義できる。
名前づけた型に、メソッドを生やすことができる。
type Age int func (a Age) IsAdult() bool { return a >= 20 }
型解析
go/types
による型解析は、
ソースコードから go/ast
で取得したASTを、
Config.Check
を使うところから始まる。
types.Package
を手に入れることができ、パッケージスコープの types.Object
を利用できる。
The Go Playground
https://play.golang.org/p/7FYX8gtU-LS
types.Object
types.Object
型は構文木のすべての識別子を表現している。(ast.Ident
型にマッピングされている)
var
や const
や type
といったものも全て types.Object
で抽象化されていて、
タイプアサーションで解析していく。
switch obj := obj.(type) { // declared `type` case *types.TypeName: // declared `const` case *types.Const: }
*ast.TypeName
型は type
演算子を用いた構文の型解析情報が含まれている。
Name() string
メソッドで定義した型の名、
Type() types.Type
メソッドで型を取得できる。
types.Type
types.Type
マジ便利。
type Age int func (a Age) IsAdult() bool { return a >= 20 }
この Age
型は types.Object
の *types.TypeName
が対応している。
そして types.Type
は *types.Named
が対応しており、
この *types.Named
から型に紐づかれたメソッドの情報を取得できる。
switch obj := obj.(type) { // declared type case *types.TypeName: named := obj.Type().(*types.Named) for i := 0; i < named.NumMethods(); i++ { fn := named.Method(i) fmt.Printf("%s\n", fn) } }
IsAdult
メソッドの情報は types.Func
型として取得できる。
https://play.golang.org/p/9G01rKmHnzj
構造体のフィールドを解析する
Human
型は構造体である struct
型から名前づけられた型。
type Human struct { Name string Age Age }
types.Type
は Underlying()
メソッドでベースとなった型情報を取得できる。
https://play.golang.org/p/IxF5E0zq7lM
Underlying の型を解析もこんな感じで行う。
typ := obj.Type() switch un := typ.Underlying().(type) { // type Foo struct case *types.Struct: // type Foo interface case *types.Interface: // type Foo []Bar case *types.Slice: // type Foo map[Bar]Baz case *types.Map: }
まとめ
静的解析ツールの開発に go/types
パッケージがあまりにも便利だった。
こういったツールチェインの豊富さが、Goコミュニティの活性化に繋がっていると実感できた。
静的解析でできることが増えそうな予感がした。 gouml
も機能を充実していきたい。