日記マン

動画広告プロダクトしてます。Go, Kubernetesが好きです。

GoのコードからPlantUMLコードを生成する静的解析ツールを作っている

tl;dr

gouml の紹介

Goのコードを静的解析し、PlantUMLのコードを吐くパーサを開発している。

github.com

例えば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図としてこのように描画される。
(描画はふつうに本家に任せた。)

f:id:i101330:20190414205002p:plain
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 "わんわん"
}

f:id:i101330:20190415001740p:plain

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 型にマッピングされている)
varconsttype といったものも全て 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.TypeUnderlying() メソッドでベースとなった型情報を取得できる。

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 も機能を充実していきたい。

Bookmark

GoのためのGo

静的解析で型を扱う #golang - Qiita