Skip to content
Hiroaki Otsu edited this page Apr 14, 2014 · 4 revisions

plsenseが行う型推論の仕様について

基本

型推論はソースコードの解析によって行われ、それは、大雑把に言えば、代入式とreturn式の収集です。

sub hoge () {
    my $hoge = shift;  # 代入式
    return $hoge;      # return式
}

これらに渡される値によって、変数やメソッド戻り値などの型を特定していきます。

文法エラー

文法エラーがあるファイルは解析できません。

各プログラミング要素


型の特定において最も優先されます。

my $hoge = "hoge";                # SCALAR
my @hoge = ("ho", "ge");          # ARRAY
my %hoge = ( name => "hoge", );   # HASH
my $hoge = [ "ho", "ge" ];        # REFERENCE of ARRAY
my $hoge = { name => "hoge", };   # REFERENCE of HASH

以下のように、解決された型が複数あると、正常に判別できる保障がなくなります。

my $hoge = [ "hoge" ];
my $fuga = {};
if ( $hoge ) { $fuga = $hoge; }  # 複数の型が代入されているので、$fugaが正常に特定できない

newという名称のメソッドは、自動的に戻り値がその所属するクラスのインスタンスになります。
その他のメソッドで、blessされたリファレンスを返しても、それが正常に判別される保障はありません。

package Hoge;
sub new { return; }                                                       # 戻り値は無条件にHogeになる
sub get_instance { Fuga->new(); }                                         # 判別可能
sub get_instance { my $cls=shift; my $r={}; bless $r, $cls; return $r; }  # 保障できない

要素番号は無視し、要素は全て同じ型であると判断します。

$hoge[0] = Hoge->new();
$hoge[1] = \%fuga;
$hoge[0]->    # Hogeのメソッドは補完されない

キーがリテラルかつ、'[a-zA-Z0-9_-]+'にマッチした時のみ、その値の判別が可能です。

$hoge{hoge} = Hoge->new();
$hoge{"fuga"} = \%fuga;
my ($foo, $bar) = ("foo", "bar");
$hoge{$foo} = Foo->new();
$hoge{$bar} = Bar->new(); # 区別できないキーの値は全てBarのインスタンスであると判断する

$hoge{hoge}->      # Hogeのメソッドが補完できる
$hoge{'fuga'}->{   # %fugaのキーが補完できる
$hoge{$foo}->      # Fooのメソッドは補完されない

変数を区別できるのは、subの中までです。

package Hoge;
my $some = Fuga->new();

sub get_hoge {
    my $some = Foo->new();
    foreach my $e ( "foo", "bar" ) {
        my $some = $e;
    }
    $some->  # もはやFooでない
}

$some->   # Fugaのメソッドが補完される
my $hoge = Hoge->new() || Fuga->new(); # Hogeのインスタンスとみなされる
my $fuga = Hoge->new() && Fuga->new(); # Fugaのインスタンスとみなされる

一番最初の要素を採用します。

my $some = $hoge ? Hoge->new()
         : $fuga ? Fuga->new()
         :         Bar->new();
$some->  # Hogeのインスタンスとみなされる

配列、ハッシュのキーの値のみ判別可能です。

my @hoge = ( @fuga, @bar );         # @fuga/@barの要素が判別できれば、@hogeの要素も判別できる
my %hoge = ( fuga => get_fuga() );  # get_fuga()が判別できれば、$hoge{fuga}も判別できる
my $hoge = { %fuga };               # 判別できない
my $hoge = [ @fuga, @bar ];         # 判別できない

裸のワードのみ判別可能です。

use Hoge;        # 判別可能
require Hoge;    # 判別可能
require "Hoge";  # 判別できない
require $class;  # 判別できない

また、プラグマの場合、その指定による効果を読み取ることは以下のもの以外はできません。

  • vars

上記プラグマについては、それが与える効果に相当する実装がされています。
詳しくは、拡張性を参照して下さい。

my $hoge = new Hoge;   # 前置呼び出しは判別できない
my $hoge = Hoge->new;  # これはOK

my $hoge = myfunc $fuga;  # $fugaはmyfuncの引数とはみなされない
my $hoge = myfunc($fuga); # $fugaはmyfuncの引数とみなされる

my $hoge = shift @fuga; # 組込み関数の場合は、括弧がなくてもOK

対応していない組込み関数は、引数も戻り値も判別できません。
現在対応しているのは、以下です。

  • bless
  • eval (BLOCKの引数の場合のみ)
  • grep
  • first
  • pop
  • push
  • reverse
  • shift
  • sort
  • undef
  • unshift
  • values

また、外部モジュールの関数で対応しているものには以下があります。

  • List::Util::first
  • List::AllUtils::first
  • List::MoreUtils::uniq
  • List::AllUtils::uniq

ここでいう、対応とは、通常の解析処理以外に、 関数が受け取る引数の判別や、戻す値に関する処理が実装されていることを示します。
詳しくは、拡張性を参照して下さい。

メソッドの引数は、通常、そのメソッド呼び出しの記述が見つかるまで、判別できません。

sub hoge {
    my $hoge = shift;
    $hoge->  # 型は不明
}

hoge( Hoge->new() );  # この記述が見つかれば、上記の$hogeの型も判明する

ただし、そのクラスがnewメソッドを持つ場合には、そのクラスのメソッドの第一引数は自身のインスタンスと判断します。

package Hoge;

sub new { my $r = {}; bless $r; return $r; }

sub hoge {
    my $hoge = shift;
    my $fuga = shift;
    $hoge->  # 無条件でHogeのインスタンスだと判断する
}

その場合、メソッド呼び出しで渡される引数は順番がずれます。

package main;
use Hoge;
Hoge->hoge( $arg );  # 上記の$fugaは$argであると判断する

本来なら、アロー演算子の前置部分が引数となるのでしょうが、本モジュールでは影響しません。

本モジュールの解析は、Perlがデフォルトで提供している記述に基づき実施され、 それ以外の記述の意味は判断できません。
つまり、外部モジュールが提供する特別な記述による効果を反映できません。

例えば、Mooseを使い、以下のように記述しても、

package Hoge;
use Moose;
has 'fuga' => ( is => 'rw', isa => 'Fuga' );

package main;
my $hoge = Hoge->new();
$hoge->fuga->  # Fugaのインスタンスである

とは、判断できません。

ただし、以下のモジュールについては、そのモジュールが提供する記述方式を認識することができます。

  • Class::Std

通常の解析処理の他に、上記モジュールのために、その記述を解析する処理が実装されています。
詳しくは、拡張性を参照して下さい。