7
5

More than 3 years have passed since last update.

AIでruby-signatureを生成する

Posted at

はじめに

タイトルはお約束の釣りでただし、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]

7
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
5