Slide 1

Slide 1 text

The Go gopher was designed by Renée French. The gopher stickers was made by Takuya Ueda. Licensed under the Creative Commons 3.0 Attributions license. 静的解析と学ぶ 型パラメタ(ジェネリクス) mercari.go #18 2022/01/20(木) 資料URL:https://tenn.in/typeparamanalysis

Slide 2

Slide 2 text

上田拓也 Go ビギナーズ 
 Go Conference 
 @tenntenn tenntenn.dev Google Developer Expert (Go) 一般社団法人 Gophers Japan 代表理事 tenntenn Conference主催 Experts Team

Slide 3

Slide 3 text

オンラインでセッションを見るときは ■ 登壇者(私)を孤独にさせないように! ● YouTubeのコメントやTwitterなどのリアクションは大きめに! ● 知らなかったことには「へぇ」 ● 知ってることには「そうそう、それ」 ● あなたの相槌で登壇者を孤独から救えます

Slide 4

Slide 4 text

書いたところ ■ はじめに ● はじめにを書くと感慨深いです ■ 0.2 入出力 ● ioパッケージを中心にio/fsパッケージにも触れてます ■ 2.1 Nature Remoによる家電の操作 ● APIクライアントについて書いてます ■ 4.1 高度なテキスト変換 ● x/text/tranformパッケージについて書いてます 参考:技術専門誌編集者とメルペイエンジニアが 2年間続けた連載「作品で魅せる Goプログラミング」のすべて

Slide 5

Slide 5 text

Go1.18 リリースパーティ ■ 型パラメタの詳細はこちらのイベントで! ● 2022年02月18日(金) 19:00 〜 ● https://gocon.connpass.com/event/234198/ ● Remoを使って開催予定! ○ YouTube Liveでの配信はありません ○ アーカイブは残す予定です

Slide 6

Slide 6 text

こちらもどうぞ ■ Go1.18最新情報 - tenntenn Conference 2022 ● 資料:https://tenn.in/go118 ● 動画:https://youtu.be/5XRXubGV3tI?t=17178

Slide 7

Slide 7 text

静的解析についてはこちら ■ 静的解析とコード生成 ● http://tenn.in/analysis ● プログラミング言語Go完全入門の第14章 ● 300+ページの超大作

Slide 8

Slide 8 text

インターンシップで学ぶ ■ Go1.18が学べる短期インターシップ(5日間) ● https://mercan.mercari.com/articles/31914/ ● 1/20 19時まで応募可 ● 学生向け ● 時給:2,500円

Slide 9

Slide 9 text

今日話すこと ■ 型パラメタ(ジェネリクス)を含むコードの静的解析 ● 型パラメタの概要 ● Goにおける静的解析の概要 ● 型パラメタを含むコードの抽象構文木 ● 型パラメタを含むコードの型情報

Slide 10

Slide 10 text

型パラメタ ■ ジェネリックな型や関数を定義できる ● 実際の型を使用する側が指定できる ● 型制約としてインタフェースが使用できる ● 型セットという概念が登場 var printStr func([]string) = Print[string] printStr([]string{"Hello, ", "playground\n"}) Print[T] Print[string] func([]string) Print[int] func([]int) T => string T => int インスタンス化 func Print[T any](s []T) { for _, v := range s { fmt.Print(v) } }

Slide 11

Slide 11 text

静的解析 ■ 静的解析とは? ● プログラムを実行せずに解析すること ● ソースコードの構造や意味を解析する ● 例:lint、コード補完、 コードフォーマッタ 11 コーディング 010100 101000 101000 コンパイル 010100 101000 101000 デプロイ リリース テスト QA 監視 静的解析

Slide 12

Slide 12 text

Goにおける静的解析のフェーズ ■ 静的解析はいくつかのフェーズに分かれている ● 後のフェーズにいくこと、さらに詳しい情報が手に入る ● 各フェーズで手に入る情報を使い分けながら解析する ● 各フェーズで手に入る情報の紐付けの仕方がキモ ○ このノードの型情報は?など 12 構文解析 型チェック 静的単一代入形式 ポインタ解析

Slide 13

Slide 13 text

静的解析に使えるパッケージ ast 抽象構文木 (AST)を提供 build パッケージに関する情報を集める constant 定数に関する型を提供 doc ドキュメントを ASTから取り出す format コードフォーマッタ機能を提供 importer コンパイラに適した Importerを提供 parser 構文解析 機能を提供 printer AST 表示機能を提供 scanner 字句解析 機能を提供 token トークンに関する型を提供 types 型チェックに関する機能を提供 13 analysis 静的解析ツールをモジュール化するパッケージ ast AST関連のユーティリティ callgraph call graph関連 cfg control flow graph関連 expect 構造化されたコメントを処理する packages Go Modulesを前提としたパッケージ情報の収集から構文解析、 型チェックまでを行うパッケージ pointer ポインタ解析 ssa Static Single Assignment (SSA) 関連 types 型情報関連 ■ 標準で用意されている(goパッケージ) ● 準標準的なものもある(golang.org/x/tools/goパッケージ) goパッケージのサブパッケージ golang.org/x/tools/goパッケージのサブパッケージ

Slide 14

Slide 14 text

■ 入力された文字列をトークンとして分解 字句解析- go/scanner,go/token IDENT ADD INT トークン ソースコード: v + 1 14

Slide 15

Slide 15 text

構文解析 - go/parser,go/ast ■ トークンを抽象構文木(AST)に変換 ● AST: Abstract Syntax Tree v + 1 IDENT ADD INT ソースコード: + v 1 BinaryExpr Ident BasicLit トークン: 抽象構文木(AST): 15

Slide 16

Slide 16 text

型チェック - go/types,go/constant ■ 型情報を抽象構文木から抽出 ● 識別子の解決 ● 型の推論 ● 定数の評価 n := 100 + 200 m := n + 300 定数の評価 = 300 型の推論 -> int 識別子の解決 16

Slide 17

Slide 17 text

静的解析ツールを簡単に作る ■ golang.org/x/tools/go/analysisパッケージ ● 静的解析ツールのモジュール化を提供するパッケージ ● Go1.12からgo vetでも使われるようになった ● 構文解析と型チェックは自動で行う ● Analyzer単位で開発する var Analyzer = &analysis.Analyzer{ Name: "simple", Doc: "simple is simple Analyzer", Run: run, Requires: []*analysis.Analyzer{inspect.Analyzer}, } func run(pass *analysis.Pass) (interface{}, error) { /* 解析処理 */ return nil, nil } 依存するAnalyzer 抽象構文木や型情報を保持

Slide 18

Slide 18 text

skeleton ■ go/analysis用のスケルトンコードジェネレータ ● https://github.com/gostaticanalysis/skeleton ● 簡単に静的解析ツールを始めることができる ● Analyzer、テストコード、main.goの雛形作ってくれる $ skeleton myanalyzer myanalyzer ├── cmd │ └── myanalyzer │ └── main.go ├── myanalyzer.go ├── myanalyzer_test.go └── testdata └── src └── a └── a.go

Slide 19

Slide 19 text

goパッケージの何が変わるのか ■ GOROOT/api/go1.18.txtを見てみよう ● https://cs.opensource.google/go/go/+/master:api/go1.18.txt ● 新しく加わる機能が分かる $ grep "go/" `go1.18beta1 env GOROOT`/api/go1.18.txt pkg go/ast, method (*IndexListExpr) End() token.Pos pkg go/ast, method (*IndexListExpr) Pos() token.Pos pkg go/ast, type FuncType struct, TypeParams *FieldList pkg go/ast, type IndexListExpr struct pkg go/ast, type IndexListExpr struct, Indices []Expr pkg go/ast, type IndexListExpr struct, Lbrack token.Pos pkg go/ast, type IndexListExpr struct, Rbrack token.Pos pkg go/ast, type IndexListExpr struct, X Expr pkg go/ast, type TypeSpec struct, TypeParams *FieldList pkg go/constant, method (Kind) String() string (略)

Slide 20

Slide 20 text

抽象構文木をダンプして確かめる ■ ast.Print関数を使う ● 第2引数のノードをダンプする関数 ■ knsh14/astreeを使う(使いたい) ● treeコマンドっぽく出すツール ● まだ未対応だけどPRは出てるっぽい ○ マージお願いします ○ https://github.com/knsh14/astree/pull/9 ast.Print(pass.Fset, pass.Files[0])

Slide 21

Slide 21 text

抽象構文木から型パラメタを得る ■ 関数(ast.FuncType構造体) ● 関数の型を表すノード ● *ast.FieldList型のTypeParamsフィールドから取得できる ● ast.Field構造体で同じ制約の型パラメタのリストを表す ○ [X, Y any, Z fmt.Stringer]だとX, Y anyとZ fmt.Stringerの単位 ○ Namesフィールドが型パラメタのスライス( []*ast.Ident型) ○ Typeフィールドが型制約(ast.Expr型) ■ 型(ast.TypeSpec構造体) ● 型宣言のtypeキーワードより後の部分を表すノード ● *ast.FieldList型のTypeParamsフィールドから取得できる func Print[T any](s []T) {...} type Vector[T any] []T

Slide 22

Slide 22 text

ノードから型情報の型パラメタを得る ■ 型パラメタの型情報をtypes.TypeParam構造体で表す ● types.Typeインタフェースを実装 ● Constraintメソッドで制約が取得できる ■ 型パラメタを表す識別子から取得できる ● 例:ast.FuncType構造体のTypeParamsフィールド ○ ast.Field構造体のNamesフィールドの要素から取得できる ● (*types.Info).TypeOfメソッドを使う

Slide 23

Slide 23 text

型情報から型パラメタを取得 ■ 関数(types.Signature型) ● TypeParamsメソッドから取得できる ● レシーバの型パラメタもRecvTypeParamsメソッドから取得可 ■ 型(types.Named型) ● TypeParamsメソッドから型パラメタを取得 ● TypeArgsメソッドから型引数が取得できる ■ 型パラメタを表すオブジェクト ● types.TypeName構造体で表される

Slide 24

Slide 24 text

制約を取得する ■ (*types.TypeParam).Constraintメソッドで取得 ● *types.Interface型で表す ● IsComparebleメソッドで比較かのうかどうか取得 ○ 組み込みのcompareble制約(インタフェース) ○ 型セットがcompareble制約のものの部分集合の制約 ● IsImplicitメソッドで暗黙のインタフェースか取得 ○ func f[T ~int]()のようにinterface{ ~int }を省略できる ● IsMethodSetメソッドでメソッドセットのみか判定 ○ 制約以外にも使えるかどうか分かる

Slide 25

Slide 25 text

型パラメタを持つ関数の呼び出し ■ 関数呼び出しはast.CallExpr構造体が表す ● Funフィールドに呼び出す関数を表すノードを保持 ● 型引数が指定してあるかどうかで型が変わる ● 型引数が1つの場合は*ast.IndexExpr型の値が入る ○ ast.IdexExpr構造体はスライスやマップでも使われる既存の型 ○ 後方互換性のためにast.IdentListExpr型に統合されなかった ● 型引数が2つ以上の場合は*ast.IndexListExpr型の値が入る ○ f[string, int]("hoge", 100)のような場合 ● 型引数なしで型推論をする場合は*ast.Ident型の値が入る ○ 関数呼び出しは型引数が省略できる ○ 抽象構文木だけでは実際の型引数が分からない

Slide 26

Slide 26 text

型パラメタのインスタンス化 ■ types.Instantiate関数を用いる ● types.Context構造体はインスタンス化された型情報などを持つ ○ 型チェック時のtypes.Config構造体で指定できる ● 第2引数の型は型パラメタを持つ型 ● 第3引数は型引数のスライス ● 第4引数はインスタンス化の検証を行うかどうか Print[T] Print[string] func([]string) Print[int] func([]int) T => string T => int インスタンス化 func Instantiate(ctxt *Context, orig Type, targs []Type, validate bool) (Type, error)

Slide 27

Slide 27 text

新しく導入されるトークン ■ ~(チルダ):token.TILDE定数 ● インタフェース要素に使える ● interface{ ~string | int }のように記述できる InterfaceType = "interface" "{" { InterfaceElem ";" } "}" . InterfaceElem = MethodElem | TypeElem . MethodElem = MethodName Signature . MethodName = identifier . TypeElem = TypeTerm { "|" TypeTerm } . TypeTerm = Type | UnderlyingType . UnderlyingType = "~" Type .

Slide 28

Slide 28 text

制約のインタフェースを表すノード ■ ast.InterfaceType構造体で表す ● *ast.FieldList型のMethodsフィールドがインタフェース要素 ○ Methodsなのは後方互換のため ● ~stringなどはast.UnaryExpr型(単項演算式)で表す ● int | ~stringなどはast.BinaryExpr型(2項演算式)で表す InterfaceType = "interface" "{" { InterfaceElem ";" } "}" . InterfaceElem = MethodElem | TypeElem . MethodElem = MethodName Signature . MethodName = identifier . TypeElem = TypeTerm { "|" TypeTerm } . TypeTerm = Type | UnderlyingType . UnderlyingType = "~" Type .

Slide 29

Slide 29 text

デモ 参考:https://github.com/gostaticanalysis/examples/tree/main/typeparam

Slide 30

Slide 30 text

まとめ ■ 型パラメタを含むコードも静的解析できる ● 標準パッケージで静的解析ができるから言語仕様に追従してくれる ● 既存の静的解析のエコシステムを壊さずに導入されている ● これから増えてくるジェネリクスのコードに対応しよう ○ さっそく、go vetのprintf.Analyzerは対応している