Skip to content

Commit 480c63b

Browse files
committed
Add a document
1 parent 9076d43 commit 480c63b

2 files changed

Lines changed: 270 additions & 0 deletions

File tree

doc/doc.ja.md

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
# TypeProf: 抽象解釈に基づくRubyの型解析器
2+
3+
## TypeProfの使い方 - CLIツールとして
4+
5+
app.rb を解析する。
6+
7+
```
8+
$ typeprof app.rb
9+
```
10+
11+
一部のメソッドの型を指定した sig/app.rbs とともに app.rb を解析する。
12+
13+
```
14+
$ typeprof sig/app.rbs app.rb
15+
```
16+
17+
典型的な使用法は次の通り。
18+
19+
```
20+
$ typeprof sig/app.rbs app.rb -o sig/app.gen.rbs
21+
```
22+
23+
## TypeProfの使い方 - Language Serverとして
24+
25+
[RubyKaigi 2024の発表資料](https://speakerdeck.com/mame/good-first-issues-of-typeprof)を参照ください。
26+
27+
## TypeProfの解析方法
28+
29+
TypeProfは、Rubyプログラムを型レベルで抽象的に実行するインタプリタです。
30+
解析対象のプログラムを実行し、メソッドが受け取ったり返したりする型、インスタンス変数に代入される型を集めて出力します。
31+
すべての値はオブジェクトそのものではなく、原則としてオブジェクトの所属するクラスに抽象化されます(次節で詳説)。
32+
33+
メソッドを呼び出す例を用いて説明します。
34+
35+
```
36+
def foo(n)
37+
p n #=> Integer
38+
n.to_s
39+
end
40+
41+
p foo(42) #=> String
42+
```
43+
44+
TypeProfの解析結果は次の通り。
45+
46+
```
47+
$ typeprof test.rb
48+
# Revealed types
49+
# test.rb:2 #=> Integer
50+
# test.rb:6 #=> String
51+
52+
# Classes
53+
class Object
54+
def foo : (Integer) -> String
55+
end
56+
```
57+
58+
`foo(42)`というメソッド呼び出しが実行されると、`Integer`オブジェクトの`42`ではなく、「`Integer`」という型(抽象値)が渡されます。
59+
メソッド`foo``n.to_s`が実行します。
60+
すると、組み込みメソッドの`Integer#to_s`が呼び出され、「String」という型が得られるので、メソッド`foo`はそれを返します。
61+
これらの実行結果の観察を集めて、TypeProfは「メソッド`foo``Integer`を受け取り、`String`を返す」という情報をRBSの形式で出力します。
62+
また、`p`の引数は`Revealed types`として出力されます。
63+
64+
インスタンス変数は、通常のRubyではオブジェクトごとに記憶される変数ですが、TypeProfではクラス単位に集約されます。
65+
66+
```
67+
class Foo
68+
def initialize
69+
@a = 42
70+
end
71+
72+
attr_accessor :a
73+
end
74+
75+
Foo.new.a = "str"
76+
77+
p Foo.new.a #=> Integer | String
78+
```
79+
80+
```
81+
$ typeprof test.rb
82+
# Revealed types
83+
# test.rb:11 #=> Integer | String
84+
85+
# Classes
86+
class Foo
87+
attr_accessor a : Integer | String
88+
def initialize : -> Integer
89+
end
90+
```
91+
92+
## TypeProfの扱う抽象値
93+
94+
前述の通り、TypeProfはRubyの値を型のようなレベルに抽象化して扱います。
95+
ただし、クラスオブジェクトなど、一部の値は抽象化しません。
96+
紛らわしいので、TypeProfが使う抽象化された値のことを「抽象値」と呼びます。
97+
98+
TypeProfが扱う抽象値は次のとおりです。
99+
100+
* クラスのインスタンス
101+
* クラスオブジェクト
102+
* シンボル
103+
* `untyped`
104+
* 抽象値のユニオン
105+
* コンテナクラスのインスタンス
106+
* Procオブジェクト
107+
108+
クラスのインスタンスはもっとも普通の値です。
109+
`Foo.new`というRubyコードが返す抽象値は、クラス`Foo`のインスタンスで、少し紛らわしいですがこれはRBS出力の中で`Foo`と表現されます。
110+
`42`という整数リテラルは`Integer`のインスタンス、`"str"`という文字列リテラルは`String`のインスタンスになります。
111+
112+
クラスオブジェクトは、クラスそのものを表す値で、たとえば定数`Integer``String`に入っているオブジェクトです。
113+
このオブジェクトは厳密にはクラス`Class`のインスタンスですが、`Class`に抽象化はされません。
114+
抽象化してしまうと、定数の参照やクラスメソッドが使えなくなるためです。
115+
116+
シンボルは、`:foo`のようなSymbolリテラルが返す値です。
117+
シンボルは、キーワード引数、JSONデータのキー、`Module#attr_reader`の引数など、具体的な値が必要になることが多いので、抽象化されません。
118+
ただし、`String#to_sym`で生成されるSymbolや、式展開を含むSymbolリテラル(`:"foo_#{ x }"`など)はクラス`Symbol`のインスタンスとして扱われます。
119+
120+
`untyped`は、解析の限界や制限などによって追跡ができない場合に生成される抽象値です。
121+
`untyped`に対する演算やメソッド呼び出しは無視され、評価結果は`untyped`となります。
122+
123+
抽象値のユニオンは、抽象値に複数の可能性があることを表現する値です。
124+
人工的ですが、`rand < 0.5 ? 42 : "str"`の結果は`Integer | String`という抽象値になります。
125+
126+
コンテナクラスのインスタンスは、ArrayやHashのように他の抽象値を要素とするオブジェクトです。
127+
いまのところ、ArrayとEnumeratorとHashのみ対応しています。
128+
詳細は後述します。
129+
130+
Procオブジェクトは、ラムダ式(`-> { ... }`)やブロック仮引数(`&blk`)で作られるクロージャです。
131+
これらは抽象化されず、コード片と結びついた具体的な値として扱われます。
132+
これらに渡された引数や返された値によってRBS出力されます。
133+
134+
TBD

doc/doc.md

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
# TypeProf: A type analysis tool for Ruby code based on abstract interpretation
2+
3+
## How to use TypeProf as a CLI tool
4+
5+
Analyze app.rb:
6+
7+
```
8+
$ typeprof app.rb
9+
```
10+
11+
Analyze app.rb with sig/app.rbs that specifies some method types:
12+
13+
```
14+
$ typeprof sig/app.rbs app.rb
15+
```
16+
17+
Here is a typical use case:
18+
19+
```
20+
$ typeprof sig/app.rbs app.rb -o sig/app.gen.rbs
21+
```
22+
23+
## How to use TypeProf as a Language Server
24+
25+
See [the slide deck of my talk in RubyKaigi 2024](https://speakerdeck.com/mame/good-first-issues-of-typeprof) for now.
26+
27+
## What is a TypeProf?
28+
29+
TypeProf is a Ruby interpreter that *abstractly* executes Ruby programs at the type level.
30+
It executes a given program and observes what types are passed to and returned from methods and what types are assigned to instance variables.
31+
All values are, in principle, abstracted to the class to which the object belongs, not the object itself (detailed in the next section).
32+
33+
Here is an example of a method call.
34+
35+
```
36+
def foo(n)
37+
p n #=> Integer
38+
n.to_s
39+
end
40+
41+
p foo(42) #=> String
42+
```
43+
44+
The analysis results of TypeProf are as follows.
45+
46+
```
47+
$ ruby exe/typeprof test.rb
48+
# Revealed types
49+
# test.rb:2 #=> Integer
50+
# test.rb:6 #=> String
51+
52+
# Classes
53+
class Object
54+
def foo : (Integer) -> String
55+
end
56+
```
57+
58+
When the method call `foo(42)` is executed, the type (abstract value) "`Integer`" is passed instead of the `Integer` object 42.
59+
The method `foo` executes `n.to_s`.
60+
Then, the built-in method `Integer#to_s` is called and you get the type "`String`", which the method `foo` returns.
61+
Collecting observations of these execution results, TypeProf outputs, "the method `foo` receives `Integer` and returns `String`" in the RBS format.
62+
Also, the argument of `p` is output in the `Revealed types` section.
63+
64+
Instance variables are stored in each object in Ruby, but are aggregated in class units in TypeProf.
65+
66+
```
67+
class Foo
68+
def initialize
69+
@a = 42
70+
end
71+
72+
attr_accessor :a
73+
end
74+
75+
Foo.new.a = "str"
76+
77+
p Foo.new.a #=> Integer | String
78+
```
79+
80+
```
81+
$ ruby exe/typeprof test.rb
82+
# Revealed types
83+
# test.rb:11 #=> Integer | String
84+
85+
# Classes
86+
class Foo
87+
attr_accessor a : Integer | String
88+
def initialize : -> Integer
89+
end
90+
```
91+
92+
93+
## Abstract values
94+
95+
As mentioned above, TypeProf abstracts almost all Ruby values to the type level, with some exceptions like class objects.
96+
To avoid confusion with normal Ruby values, we use the word "abstract value" to refer the values that TypeProf handles.
97+
98+
TypeProf handles the following abstract values.
99+
100+
* Instance of a class
101+
* Class object
102+
* Symbol
103+
* `untyped`
104+
* Union of abstract values
105+
* Instance of a container class
106+
* Proc object
107+
108+
Instances of classes are the most common values.
109+
A Ruby code `Foo.new` returns an instance of the class `Foo`.
110+
This abstract value is represented as `Foo` in the RBS format, though it is a bit confusing.
111+
The integer literal `42` generates an instance of `Integer` and the string literal `"str"` generates an instance of `String`.
112+
113+
A class object is a value that represents the class itself.
114+
For example, the constants `Integer` and `String` has class objects.
115+
In Ruby semantics, a class object is an instance of the class `Class`, but it is not abstracted into `Class` in TypeProf.
116+
This is because, if it is abstracted, TypeProf cannot handle constant references and class methods correctly.
117+
118+
A symbol is an abstract value returned by Symbol literals like `:foo`.
119+
A symbol object is not abstracted to an instance of the class `Symbol` because its concrete value is often required in many cases, such as keyword arguments, JSON data keys, the argument of `Module#attr_reader`, etc.
120+
Note that some Symbol objects are handled as instances of the class `Symbol`, for example, the return value of `String#to_sym` and Symbol literals that contains interpolation like `:"foo_#{ x }"`.
121+
122+
`untyped` is an abstract value generated when TypeProf fails to trace values due to analysis limits or restrictions.
123+
Any operations and method calls on `untyped` are ignored, and the evaluation result is also `untyped`.
124+
125+
A union of abstract values is a value that represents multiple possibilities.,
126+
For (a bit artificial) example, the result of `rand < 0.5 ? 42 : "str"` is a union, `Integer | String`.
127+
128+
An instance of a container class, such as Array and Hash, is an object that contains other abstract values as elements.
129+
At present, only Array, Enumerator and Hash are supported.
130+
Details will be described later.
131+
132+
A Proc object is a closure produced by lambda expressions (`-> {... }`) and block parameters (`&blk`).
133+
During the interpretation, these objects are not abstracted but treated as concrete values associated with a piece of code.
134+
In the RBS result, they are represented by using anonymous proc type, whose types they accepted and returned.
135+
136+
TODO: write more

0 commit comments

Comments
 (0)