4.1 KiB
属性の解決
属性の解決とは、例えばx.yという式が与えられたときにこの式全体の型を決定することを指します。従ってxの型を決定する必要がありますが、xの型は一意に決定できない場合があります。そのような場合でもx.yの型は決定できる場合がありますし、失敗する場合もあります。これが本項が扱う属性の解決の問題です。
簡単な場合として、1.real(x == 1, y == real)という式を考えてみましょう。1の型は{1}です。{1}はNatやInt、Objの部分型です。これらの型を順番に辿って、realの定義を探します。この場合はIntで見つかります(Int.real: Int)。従ってxの型はIntにキャストされ、1.realの型はIntとなります。
このように、xの型が一意に特定出来る場合は左辺x->右辺yという順番で推論が進みます。
しかしxの型が特定できないとき、逆にyからxの型が絞られることもあります。
例えば、このような場合です。
consts c = c.co_consts
co_constsはCode型の属性です。この関数の意味するところは本質ではなく、単に他と被らない名前であるからこの例を選択しました。
cの型が指定されていないので、一見推論はできないように見えますが、(名前空間中にco_constsを持つ型がCodeしかないときは)可能です。
Ergでは変数の型が指定されていないとき、型変数が割り当てられます。
consts: ?1
c: ?2
型推論器は?2型からco_constsの所属を特定しようとしますが、?2は何の条件もついていない型変数なので、失敗します。
このような場合、get_attr_type_by_nameというメソッドが呼ばれます。
このメソッドでは、これまでとは逆に、co_constsという名前から?2の型を特定しようとします。
これが成功するのは、名前空間中にco_constsを持つ型がCodeしかないときのみです。
Ergでは関数の型検査はモジュール内で閉じているので、モジュール外でco_constsを属性に持つ型が定義されていても、そのインスタンスをconsts関数に渡すとエラーになります(それを可能にするためには、後述するStructuralを使う必要があります)。この制約によってconsts関数の推論が可能になります。
型推論器は、クラス属性が定義されるとき、その"属性"と"定義クラス、属性の型"のペアを記録しておきます。
co_constsの場合は{co_consts: {Code, List(Obj, _)}}というペアです。
method_to_classes: {co_consts: [{Code, List(Obj, _)}], real: [{Int, Int}], times!: [{Nat, (self: Nat, proc!: () => NoneType) => NoneType}], ...}
key-valueペアのvalueが配列になっていることに注意してください。この配列が長さ1であるとき、または(部分型関係による)最小の要素が存在するときのみ、keyは一意に特定できたということになります(そうでなければ型エラーが発生します)。
keyが特定できたら、その定義型を?2の型に逆伝搬させます。
?2(<: Code).co_consts: List(Obj, _)
最終的に、constsの型はCode -> List(Obj, _)となります。
consts(c: Code): List(Obj, _) = c.co_consts