はじめに
タイトルはお約束の釣りでただし、AIはAbstract Interpretationですって奴です。
ruby-signatureを抽象実行で生成しようってのがうまくいきそう(うまくいったとは言っていない)のでまとめてみます。
ruby-signatureって
ruby-signatureはRubyのライブラリや組み込みクラス・メソッド等の戻り値の型情報を記述するための記法と型のデータベースです。ここ にあります。
型情報は手で書くことを想定しているわけですが、自動的に生成出来れば色々便利でしょう。
mmcについて
mmcは私が作っているRubyからCに変換するコンパイラです。中に抽象実行をおこなって型を推定するエンジンを持っています。抽象実行とかそういうのは書かないのでここを見てください。
Enumerableの型を解析する
おそらくとりあえず一番むつかしいのはEnumerableじゃないかと思われます。これはModuleでそのままではインスタンスを作ることもできません。また、どのクラスにincludeされるかで結果の型が変ってきます。そのため、型変数を含んだ型情報になります。
Enumerableの正解はこちらになります。
型変数の導入
これまで実際の値を用意できないと抽象実行出来なかったのですが、型変数(TypeVariable)というクラスを用意します。このクラスは任意のメソッドを呼び出したら、戻り値として別の型変数を用意して、呼び出されたメソッド名とその戻りの型を記録しておきます。また、引数にブロックやProcオブジェクトがあった場合は、引数として別の型変数を渡してProcオブジェクトを抽象実行します。この辺のコードはこちらになります。method_missingとして実装しています。
define_inf_rule_method :missing, TypeVariable do |infer, inst, node, tup|
rec = inst.inreg[0].type[tup][0]
name = inst.inreg[1].type[tup][0]
block = inst.inreg[3].type[tup][0]
if block.is_a?(ProcType) then
make_intype(infer, inst, node, tup) do |intype, argc|
# intype = inst.inreg.map {|reg| reg.flush_type(tup)[tup] || []}
intype[0] = [block.slf]
ele = rec.sub_type_var[:ele] ? rec.sub_type_var[:ele] : TypeVarType.new(TypeVariable)
rec.sub_type_var[:ele] = ele
intype[1] = [ele]
intype[2] = inst.inreg[3].type[tup]
ntup = infer.typetupletab.get_tupple_id(intype, block, tup)
irepssa = block.irep
infer.inference_block(irepssa, intype, ntup, argc, block)
inst.outreg[0].add_same irepssa.retreg
inst.outreg[0].flush_type(tup, ntup)
end
end
ret = rec.sub_type_var[:ret] || TypeVarType.new(TypeVariable)
rec.using_method[name] = [inst.inreg.map {|r| r.type[tup]}, ret]
rec.sub_type_var[:ret] = ret
inst.outreg[0].add_type ret, tup
nil
end
入力プログラム
Enumerableモジュールを型解析するための入力プログラムはこんな感じです
class TypeVariable
include Enumerable
end
class Foo<TypeVariable
end
MTypeInf::inference_main {
slf = Foo.new
blk = lambda {|a| TypeVariable.new}
slf.collect(&blk)
blk = lambda {|a| TypeVariable.new}
slf.any?(&blk)
blk = lambda {|a| TypeVariable.new}
slf.detect(TypeVariable.new, &blk)
blk = lambda {|a| TypeVariable.new}
slf.each_with_index(&blk)
blk = lambda {|a| TypeVariable.new}
slf.entries(&blk)
}
さすがにEnumerableは単独でどう頑張ってもインスタンス化できないので、型変数クラスにincludeしています。あとは、引数をすべて型変数クラスのインスタンスを渡してやればOKです。引数の数が分かれば自動生成も可能でしょう。
表示
解析結果を表示する場所です
if cls == TypeVariable then
acls = cls.ancestors[1]
mblk = clsobj.method.values[0]
sreg = mblk.regtab[0]
slftype = sreg.type.values[0][0]
tvpara = slftype.sub_type_var.map {|name, ty| ty }
interface = slftype.using_method.map {|name, ty|
name.val
}
mess << "#{acls.class} #{acls} #{tvpara} #{interface} \n"
else
TypeVariableがselfになっているメソッドはEnumerableなどのModuleです。この場合は型変数型でメソッド呼び出しの度に集めたメソッド情報とその戻り値の型情報をダンプしています。
結果
これでこんな感じの出力になります
Module Enumerable [TV1, TV4] [:each]
Instance variables
methodes
collect (TV0, (Object, TV1, NilClass) -> TV2 ) -> Array<TV2>
method_missing (TV0, Symbol(:each), (TV0, TV1, Proc<>, Proc<>) -> Array<TV1> ) -> Array<TV1>|TV4
any? (TV0, (Object, TV1, NilClass) -> TV5 ) -> TrueClass|FalseClass
detect (TV0, TV1, (Object, TV1, NilClass) -> TV8 ) -> TV1
each_with_index (TV0, (Object, TV1, Fixnum, NilClass) -> TV10
(Object, TV1, Fixnum, NilClass) -> TV12 ) -> TV0
entries (TV0, ) -> Array<TV1>
まだまだ不完全ですけど、結構いい線行っているのではいでしょうか? (ひいき目です)
お手本はこんな感じ
module Enumerable[unchecked out Elem, out Return]: _Each[Elem, Return]
def `any?`: () -> bool
| () { (Elem arg0) -> untyped } -> bool
def collect: [U] () { (Elem arg0) -> U } -> ::Array[U]
| () -> ::Enumerator[Elem, Return]
def detect: (?Proc ifnone) { (Elem arg0) -> untyped } -> Elem?
| (?Proc ifnone) -> ::Enumerator[Elem, Return]
def each_with_index: () { (Elem arg0, Integer arg1) -> untyped } -> ::Enumerable[Elem, Return]
| () -> ::Enumerator[[ Elem, Integer ], Return]
def each_with_object: [U] (U arg0) { (Elem arg0, untyped arg1) -> untyped } -> U
| [U] (U arg0) -> ::Enumerator[[ Elem, U ], Return]