typeprof 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.github/workflows/main.yml +26 -0
- data/.gitignore +7 -0
- data/.gitmodules +6 -0
- data/Gemfile +12 -0
- data/Gemfile.lock +41 -0
- data/README.md +53 -0
- data/Rakefile +10 -0
- data/doc/doc.ja.md +415 -0
- data/doc/doc.md +429 -0
- data/doc/ppl2019.pdf +0 -0
- data/exe/typeprof +5 -0
- data/lib/typeprof.rb +13 -0
- data/lib/typeprof/analyzer.rb +1911 -0
- data/lib/typeprof/builtin.rb +554 -0
- data/lib/typeprof/cli.rb +110 -0
- data/lib/typeprof/container-type.rb +626 -0
- data/lib/typeprof/export.rb +203 -0
- data/lib/typeprof/import.rb +546 -0
- data/lib/typeprof/insns-def.rb +61 -0
- data/lib/typeprof/iseq.rb +387 -0
- data/lib/typeprof/method.rb +267 -0
- data/lib/typeprof/type.rb +1092 -0
- data/lib/typeprof/utils.rb +209 -0
- data/run.sh +3 -0
- data/smoke/alias.rb +30 -0
- data/smoke/alias2.rb +19 -0
- data/smoke/any-cbase.rb +5 -0
- data/smoke/any1.rb +15 -0
- data/smoke/any2.rb +17 -0
- data/smoke/arguments.rb +16 -0
- data/smoke/array-each.rb +14 -0
- data/smoke/array-each2.rb +15 -0
- data/smoke/array-each3.rb +15 -0
- data/smoke/array-ltlt.rb +13 -0
- data/smoke/array-ltlt2.rb +16 -0
- data/smoke/array-map.rb +11 -0
- data/smoke/array-map2.rb +10 -0
- data/smoke/array-map3.rb +22 -0
- data/smoke/array-mul.rb +17 -0
- data/smoke/array-plus1.rb +10 -0
- data/smoke/array-plus2.rb +15 -0
- data/smoke/array-pop.rb +11 -0
- data/smoke/array-replace.rb +12 -0
- data/smoke/array-s-aref.rb +11 -0
- data/smoke/array1.rb +26 -0
- data/smoke/array10.rb +14 -0
- data/smoke/array11.rb +13 -0
- data/smoke/array12.rb +24 -0
- data/smoke/array13.rb +30 -0
- data/smoke/array14.rb +13 -0
- data/smoke/array2.rb +27 -0
- data/smoke/array3.rb +25 -0
- data/smoke/array4.rb +14 -0
- data/smoke/array5.rb +13 -0
- data/smoke/array6.rb +14 -0
- data/smoke/array7.rb +13 -0
- data/smoke/array8.rb +13 -0
- data/smoke/array9.rb +12 -0
- data/smoke/attr.rb +28 -0
- data/smoke/backtrace.rb +32 -0
- data/smoke/block1.rb +22 -0
- data/smoke/block10.rb +14 -0
- data/smoke/block11.rb +39 -0
- data/smoke/block12.rb +22 -0
- data/smoke/block2.rb +14 -0
- data/smoke/block3.rb +38 -0
- data/smoke/block4.rb +18 -0
- data/smoke/block5.rb +18 -0
- data/smoke/block6.rb +20 -0
- data/smoke/block7.rb +20 -0
- data/smoke/block8.rb +27 -0
- data/smoke/block9.rb +12 -0
- data/smoke/blown.rb +12 -0
- data/smoke/break1.rb +18 -0
- data/smoke/break2.rb +15 -0
- data/smoke/case.rb +16 -0
- data/smoke/case2.rb +17 -0
- data/smoke/class.rb +5 -0
- data/smoke/class_instance_var.rb +9 -0
- data/smoke/class_method.rb +25 -0
- data/smoke/class_method2.rb +21 -0
- data/smoke/class_method3.rb +27 -0
- data/smoke/constant1.rb +38 -0
- data/smoke/constant2.rb +33 -0
- data/smoke/constant3.rb +9 -0
- data/smoke/constant4.rb +11 -0
- data/smoke/context-sensitive1.rb +12 -0
- data/smoke/cvar.rb +28 -0
- data/smoke/cvar2.rb +17 -0
- data/smoke/demo.rb +80 -0
- data/smoke/demo1.rb +16 -0
- data/smoke/demo10.rb +20 -0
- data/smoke/demo11.rb +11 -0
- data/smoke/demo2.rb +14 -0
- data/smoke/demo3.rb +16 -0
- data/smoke/demo4.rb +27 -0
- data/smoke/demo5.rb +13 -0
- data/smoke/demo6.rb +21 -0
- data/smoke/demo7.rb +14 -0
- data/smoke/demo8.rb +18 -0
- data/smoke/demo9.rb +18 -0
- data/smoke/dummy-execution1.rb +14 -0
- data/smoke/dummy-execution2.rb +16 -0
- data/smoke/ensure1.rb +20 -0
- data/smoke/enumerator.rb +15 -0
- data/smoke/expandarray1.rb +22 -0
- data/smoke/expandarray2.rb +23 -0
- data/smoke/fib.rb +28 -0
- data/smoke/flow1.rb +16 -0
- data/smoke/flow2.rb +14 -0
- data/smoke/flow3.rb +14 -0
- data/smoke/flow4.rb +5 -0
- data/smoke/flow5.rb +19 -0
- data/smoke/flow6.rb +19 -0
- data/smoke/flow7.rb +26 -0
- data/smoke/for.rb +9 -0
- data/smoke/freeze.rb +11 -0
- data/smoke/function.rb +16 -0
- data/smoke/gvar.rb +13 -0
- data/smoke/hash-fetch.rb +27 -0
- data/smoke/hash1.rb +18 -0
- data/smoke/hash2.rb +12 -0
- data/smoke/hash3.rb +13 -0
- data/smoke/hash4.rb +10 -0
- data/smoke/hash5.rb +14 -0
- data/smoke/inheritance.rb +34 -0
- data/smoke/inheritance2.rb +29 -0
- data/smoke/initialize.rb +26 -0
- data/smoke/instance_eval.rb +18 -0
- data/smoke/int_times.rb +14 -0
- data/smoke/integer.rb +10 -0
- data/smoke/ivar.rb +29 -0
- data/smoke/ivar2.rb +30 -0
- data/smoke/kernel-class.rb +12 -0
- data/smoke/keyword1.rb +11 -0
- data/smoke/keyword2.rb +11 -0
- data/smoke/keyword3.rb +12 -0
- data/smoke/keyword4.rb +11 -0
- data/smoke/keyword5.rb +15 -0
- data/smoke/kwsplat1.rb +42 -0
- data/smoke/kwsplat2.rb +12 -0
- data/smoke/manual-rbs.rb +27 -0
- data/smoke/manual-rbs.rbs +3 -0
- data/smoke/manual-rbs2.rb +20 -0
- data/smoke/manual-rbs2.rbs +8 -0
- data/smoke/masgn1.rb +13 -0
- data/smoke/masgn2.rb +17 -0
- data/smoke/masgn3.rb +12 -0
- data/smoke/method_in_branch.rb +22 -0
- data/smoke/module1.rb +29 -0
- data/smoke/module2.rb +28 -0
- data/smoke/module3.rb +33 -0
- data/smoke/module4.rb +29 -0
- data/smoke/module_function1.rb +28 -0
- data/smoke/module_function2.rb +28 -0
- data/smoke/multiple-include.rb +14 -0
- data/smoke/multiple-superclass.rb +16 -0
- data/smoke/next1.rb +20 -0
- data/smoke/next2.rb +16 -0
- data/smoke/object-send1.rb +22 -0
- data/smoke/once.rb +12 -0
- data/smoke/optional1.rb +13 -0
- data/smoke/optional2.rb +15 -0
- data/smoke/parameterizedd-self.rb +18 -0
- data/smoke/pathname1.rb +13 -0
- data/smoke/pathname2.rb +13 -0
- data/smoke/printf.rb +20 -0
- data/smoke/proc.rb +19 -0
- data/smoke/proc2.rb +16 -0
- data/smoke/proc3.rb +14 -0
- data/smoke/proc4.rb +11 -0
- data/smoke/range.rb +13 -0
- data/smoke/redo1.rb +21 -0
- data/smoke/redo2.rb +22 -0
- data/smoke/req-keyword.rb +12 -0
- data/smoke/rescue1.rb +20 -0
- data/smoke/rescue2.rb +22 -0
- data/smoke/respond_to.rb +22 -0
- data/smoke/rest-farg.rb +10 -0
- data/smoke/rest1.rb +25 -0
- data/smoke/rest2.rb +30 -0
- data/smoke/rest3.rb +36 -0
- data/smoke/rest4.rb +18 -0
- data/smoke/rest5.rb +10 -0
- data/smoke/rest6.rb +11 -0
- data/smoke/retry1.rb +20 -0
- data/smoke/return.rb +13 -0
- data/smoke/reveal.rb +13 -0
- data/smoke/singleton_class.rb +8 -0
- data/smoke/singleton_method.rb +9 -0
- data/smoke/step.rb +17 -0
- data/smoke/string-split.rb +11 -0
- data/smoke/struct.rb +9 -0
- data/smoke/struct2.rb +24 -0
- data/smoke/super1.rb +50 -0
- data/smoke/super2.rb +16 -0
- data/smoke/super3.rb +19 -0
- data/smoke/svar1.rb +12 -0
- data/smoke/tap1.rb +17 -0
- data/smoke/toplevel.rb +12 -0
- data/smoke/two-map.rb +17 -0
- data/smoke/type_var.rb +10 -0
- data/smoke/typed_method.rb +15 -0
- data/smoke/union-recv.rb +29 -0
- data/smoke/variadic1.rb.notyet +5 -0
- data/smoke/wrong-extend.rb +25 -0
- data/smoke/wrong-include.rb +26 -0
- data/smoke/wrong-rbs.rb +15 -0
- data/smoke/wrong-rbs.rbs +7 -0
- data/testbed/ao.rb +297 -0
- data/testbed/diff-lcs-entrypoint.rb +4 -0
- data/testbed/goodcheck-Gemfile.lock +51 -0
- data/tools/coverage.rb +14 -0
- data/tools/setup-insns-def.rb +30 -0
- data/tools/stackprof-wrapper.rb +10 -0
- data/typeprof.gemspec +24 -0
- metadata +262 -0
|
@@ -0,0 +1,1092 @@
|
|
|
1
|
+
module TypeProf
|
|
2
|
+
class Type # or AbstractValue
|
|
3
|
+
include Utils::StructuralEquality
|
|
4
|
+
|
|
5
|
+
def initialize
|
|
6
|
+
raise "cannot instanciate abstract type"
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
Builtin = {}
|
|
10
|
+
|
|
11
|
+
def globalize(_env, _visited, _depth)
|
|
12
|
+
self
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def localize(env, _alloc_site, _depth)
|
|
16
|
+
return env, self
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def limit_size(limit)
|
|
20
|
+
self
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def consistent?(other, subst)
|
|
24
|
+
case other
|
|
25
|
+
when Type::Any then true
|
|
26
|
+
when Type::Var then other.add_subst!(self, subst)
|
|
27
|
+
when Type::Union
|
|
28
|
+
other.types.each do |ty2|
|
|
29
|
+
return true if consistent?(ty2, subst)
|
|
30
|
+
end
|
|
31
|
+
else
|
|
32
|
+
self == other
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def each_child
|
|
37
|
+
yield self
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def each_child_global
|
|
41
|
+
yield self
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def union(other)
|
|
45
|
+
return self if self == other # fastpath
|
|
46
|
+
|
|
47
|
+
ty1, ty2 = self, other
|
|
48
|
+
|
|
49
|
+
ty1 = container_to_union(ty1)
|
|
50
|
+
ty2 = container_to_union(ty2)
|
|
51
|
+
|
|
52
|
+
if ty1.is_a?(Union) && ty2.is_a?(Union)
|
|
53
|
+
ty = ty1.types.sum(ty2.types)
|
|
54
|
+
all_elems = ty1.elems.dup || {}
|
|
55
|
+
ty2.elems&.each do |key, elems|
|
|
56
|
+
all_elems[key] = union_elems(all_elems[key], elems)
|
|
57
|
+
end
|
|
58
|
+
all_elems = nil if all_elems.empty?
|
|
59
|
+
|
|
60
|
+
Type::Union.new(ty, all_elems).normalize
|
|
61
|
+
else
|
|
62
|
+
ty1, ty2 = ty2, ty1 if ty2.is_a?(Union)
|
|
63
|
+
if ty1.is_a?(Union)
|
|
64
|
+
Type::Union.new(ty1.types.add(ty2), ty1.elems).normalize
|
|
65
|
+
else
|
|
66
|
+
Type::Union.new(Utils::Set[ty1, ty2], nil).normalize
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
private def container_to_union(ty)
|
|
72
|
+
case ty
|
|
73
|
+
when Type::Array, Type::Hash
|
|
74
|
+
Type::Union.new(Utils::Set[], { [ty.class, ty.base_type] => ty.elems })
|
|
75
|
+
else
|
|
76
|
+
ty
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
private def union_elems(e1, e2)
|
|
81
|
+
if e1
|
|
82
|
+
if e2
|
|
83
|
+
e1.union(e2)
|
|
84
|
+
else
|
|
85
|
+
e1
|
|
86
|
+
end
|
|
87
|
+
else
|
|
88
|
+
e2
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def substitute(_subst, _depth)
|
|
93
|
+
raise "cannot substitute abstract type: #{ self.class }"
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
DummySubstitution = Object.new
|
|
97
|
+
def DummySubstitution.[](_)
|
|
98
|
+
Type.any
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def remove_type_vars
|
|
102
|
+
substitute(DummySubstitution, Config.options[:type_depth_limit])
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
class Any < Type
|
|
106
|
+
def initialize
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def inspect
|
|
110
|
+
"Type::Any"
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def screen_name(scratch)
|
|
114
|
+
"untyped"
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def get_method(mid, scratch)
|
|
118
|
+
nil
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def consistent?(other, subst)
|
|
122
|
+
# need to create a type assignment if other is Var
|
|
123
|
+
other.add_subst!(self, subst) if other.is_a?(Type::Var)
|
|
124
|
+
true
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def substitute(_subst, _depth)
|
|
128
|
+
self
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
class Union < Type
|
|
133
|
+
def initialize(tys, elems)
|
|
134
|
+
raise unless tys.is_a?(Utils::Set)
|
|
135
|
+
@types = tys # Set
|
|
136
|
+
|
|
137
|
+
# invariant check
|
|
138
|
+
local = nil
|
|
139
|
+
tys.each do |ty|
|
|
140
|
+
raise unless ty.is_a?(Type)
|
|
141
|
+
local = true if ty.is_a?(LocalArray) || ty.is_a?(LocalHash)
|
|
142
|
+
end
|
|
143
|
+
raise if local && elems
|
|
144
|
+
|
|
145
|
+
@elems = elems
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def limit_size(limit)
|
|
149
|
+
return Type.any if limit <= 0
|
|
150
|
+
tys = Utils::Set[]
|
|
151
|
+
@types.each do |ty|
|
|
152
|
+
tys = tys.add(ty.limit_size(limit - 1))
|
|
153
|
+
end
|
|
154
|
+
elems = @elems&.to_h do |key, elems|
|
|
155
|
+
[key, elems.limit_size(limit - 1)]
|
|
156
|
+
end
|
|
157
|
+
Union.new(tys, elems)
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
attr_reader :types, :elems
|
|
161
|
+
|
|
162
|
+
def normalize
|
|
163
|
+
if @types.size == 1 && !@elems
|
|
164
|
+
@types.each {|ty| return ty }
|
|
165
|
+
elsif @types.size == 0
|
|
166
|
+
if @elems && @elems.size == 1
|
|
167
|
+
(container_kind, base_type), elems = @elems.first
|
|
168
|
+
# container_kind = Type::Array or Type::Hash
|
|
169
|
+
container_kind.new(elems, base_type)
|
|
170
|
+
else
|
|
171
|
+
self
|
|
172
|
+
end
|
|
173
|
+
else
|
|
174
|
+
self
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def each_child(&blk) # local
|
|
179
|
+
@types.each(&blk)
|
|
180
|
+
raise if @elems
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def each_child_global(&blk)
|
|
184
|
+
@types.each(&blk)
|
|
185
|
+
@elems&.each do |(container_kind, base_type), elems|
|
|
186
|
+
yield container_kind.new(elems, base_type)
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def inspect
|
|
191
|
+
a = []
|
|
192
|
+
a << "Type::Union{#{ @types.to_a.map {|ty| ty.inspect }.join(", ") }"
|
|
193
|
+
@elems&.each do |(container_kind, base_type), elems|
|
|
194
|
+
a << ", #{ container_kind.new(elems, base_type).inspect }"
|
|
195
|
+
end
|
|
196
|
+
a << "}"
|
|
197
|
+
a.join
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
def screen_name(scratch)
|
|
201
|
+
types = @types.to_a
|
|
202
|
+
@elems&.each do |(container_kind, base_type), elems|
|
|
203
|
+
types << container_kind.new(elems, base_type)
|
|
204
|
+
end
|
|
205
|
+
if types.size == 0
|
|
206
|
+
"bot"
|
|
207
|
+
else
|
|
208
|
+
types = types.to_a
|
|
209
|
+
optional = !!types.delete(Type::Instance.new(Type::Builtin[:nil]))
|
|
210
|
+
bool = false
|
|
211
|
+
if types.include?(Type::Instance.new(Type::Builtin[:false])) &&
|
|
212
|
+
types.include?(Type::Instance.new(Type::Builtin[:true]))
|
|
213
|
+
types.delete(Type::Instance.new(Type::Builtin[:false]))
|
|
214
|
+
types.delete(Type::Instance.new(Type::Builtin[:true]))
|
|
215
|
+
bool = true
|
|
216
|
+
end
|
|
217
|
+
types.delete(Type.any) unless Config.options[:pedantic_output]
|
|
218
|
+
types = types.map {|ty| ty.screen_name(scratch) }
|
|
219
|
+
types << "bool" if bool
|
|
220
|
+
types = types.sort
|
|
221
|
+
if optional
|
|
222
|
+
if types.size == 1
|
|
223
|
+
types.first + "?"
|
|
224
|
+
else
|
|
225
|
+
"(#{ types.join (" | ") })?"
|
|
226
|
+
end
|
|
227
|
+
else
|
|
228
|
+
types.join (" | ")
|
|
229
|
+
end
|
|
230
|
+
end
|
|
231
|
+
rescue SystemStackError
|
|
232
|
+
p self
|
|
233
|
+
raise
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
def globalize(env, visited, depth)
|
|
237
|
+
return Type.any if depth <= 0
|
|
238
|
+
tys = Utils::Set[]
|
|
239
|
+
raise if @elems
|
|
240
|
+
|
|
241
|
+
elems = {}
|
|
242
|
+
@types.each do |ty|
|
|
243
|
+
ty = ty.globalize(env, visited, depth - 1)
|
|
244
|
+
case ty
|
|
245
|
+
when Type::Array, Type::Hash
|
|
246
|
+
key = [ty.class, ty.base_type]
|
|
247
|
+
elems[key] = union_elems(elems[key], ty.elems)
|
|
248
|
+
else
|
|
249
|
+
tys = tys.add(ty)
|
|
250
|
+
end
|
|
251
|
+
end
|
|
252
|
+
elems = nil if elems.empty?
|
|
253
|
+
|
|
254
|
+
Type::Union.new(tys, elems).normalize
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
def localize(env, alloc_site, depth)
|
|
258
|
+
return env, Type.any if depth <= 0
|
|
259
|
+
tys = @types.map do |ty|
|
|
260
|
+
alloc_site2 = alloc_site.add_id(ty)
|
|
261
|
+
env, ty2 = ty.localize(env, alloc_site2, depth - 1)
|
|
262
|
+
ty2
|
|
263
|
+
end
|
|
264
|
+
@elems&.each do |(container_kind, base_type), elems|
|
|
265
|
+
ty = container_kind.new(elems, base_type)
|
|
266
|
+
alloc_site2 = alloc_site.add_id(container_kind.name.to_sym).add_id(base_type)
|
|
267
|
+
env, ty = ty.localize(env, alloc_site2, depth - 1)
|
|
268
|
+
tys = tys.add(ty)
|
|
269
|
+
end
|
|
270
|
+
ty = Union.new(tys, nil).normalize
|
|
271
|
+
return env, ty
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
def consistent?(other, subst)
|
|
275
|
+
case other
|
|
276
|
+
when Type::Any then true
|
|
277
|
+
when Type::Var then other.add_subst!(self, subst)
|
|
278
|
+
when Type::Union
|
|
279
|
+
# this is very conservative to create subst:
|
|
280
|
+
# consistent?( int | str, int | X) creates { X => int | str } but should be { X => str }???
|
|
281
|
+
@types.each do |ty1|
|
|
282
|
+
other.types.each do |ty2|
|
|
283
|
+
subst2 = subst.dup
|
|
284
|
+
if ty1.consistent?(ty2, subst2)
|
|
285
|
+
subst.replace(subst2)
|
|
286
|
+
# XXX: need to check other pairs to create conservative substitution??
|
|
287
|
+
# consistent?( X | :foo, str | int ) may return { X => str } or { X => int } but should be { X => str | int }?
|
|
288
|
+
return true
|
|
289
|
+
end
|
|
290
|
+
end
|
|
291
|
+
end
|
|
292
|
+
return true if @types.size == 0 && other.types.size == 0 # XXX: is this okay?
|
|
293
|
+
# TODO: array argument?
|
|
294
|
+
return false
|
|
295
|
+
else
|
|
296
|
+
@types.each do |ty1|
|
|
297
|
+
return true if ty1.consistent?(other, subst)
|
|
298
|
+
end
|
|
299
|
+
# TODO: array argument?
|
|
300
|
+
return false
|
|
301
|
+
end
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
def substitute(subst, depth)
|
|
305
|
+
return Type.any if depth <= 0
|
|
306
|
+
unions = []
|
|
307
|
+
tys = Utils::Set[]
|
|
308
|
+
@types.each do |ty|
|
|
309
|
+
ty = ty.substitute(subst, depth - 1)
|
|
310
|
+
case ty
|
|
311
|
+
when Union
|
|
312
|
+
unions << ty
|
|
313
|
+
else
|
|
314
|
+
tys = tys.add(ty)
|
|
315
|
+
end
|
|
316
|
+
end
|
|
317
|
+
elems = @elems&.to_h do |(container_kind, base_type), elems|
|
|
318
|
+
[[container_kind, base_type], elems.substitute(subst, depth - 1)]
|
|
319
|
+
end
|
|
320
|
+
ty = Union.new(tys, elems)
|
|
321
|
+
unions.each do |ty0|
|
|
322
|
+
ty = ty.union(ty0)
|
|
323
|
+
end
|
|
324
|
+
ty
|
|
325
|
+
end
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
def self.any
|
|
329
|
+
@any ||= Any.new
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
def self.bot
|
|
333
|
+
@bot ||= Union.new(Utils::Set[], nil)
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
def self.bool
|
|
337
|
+
@bool ||= Union.new(Utils::Set[
|
|
338
|
+
Instance.new(Type::Builtin[:true]),
|
|
339
|
+
Instance.new(Type::Builtin[:false])
|
|
340
|
+
], nil)
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
def self.nil
|
|
344
|
+
@nil ||= Instance.new(Type::Builtin[:nil])
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
def self.optional(ty)
|
|
348
|
+
ty.union(Type.nil)
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
class Var < Type
|
|
352
|
+
def initialize(name)
|
|
353
|
+
@name = name
|
|
354
|
+
end
|
|
355
|
+
|
|
356
|
+
def screen_name(scratch)
|
|
357
|
+
"Var[#{ @name }]"
|
|
358
|
+
end
|
|
359
|
+
|
|
360
|
+
def substitute(subst, depth)
|
|
361
|
+
if subst[self]
|
|
362
|
+
subst[self].limit_size(depth)
|
|
363
|
+
else
|
|
364
|
+
self
|
|
365
|
+
end
|
|
366
|
+
end
|
|
367
|
+
|
|
368
|
+
def consistent?(other, subst)
|
|
369
|
+
raise "should not be called: #{ self }"
|
|
370
|
+
end
|
|
371
|
+
|
|
372
|
+
def add_subst!(ty, subst)
|
|
373
|
+
if subst[self]
|
|
374
|
+
subst[self] = subst[self].union(ty)
|
|
375
|
+
else
|
|
376
|
+
subst[self] = ty
|
|
377
|
+
end
|
|
378
|
+
true
|
|
379
|
+
end
|
|
380
|
+
end
|
|
381
|
+
|
|
382
|
+
class Class < Type # or Module
|
|
383
|
+
def initialize(kind, idx, type_params, superclass, name)
|
|
384
|
+
@kind = kind # :class | :module
|
|
385
|
+
@idx = idx
|
|
386
|
+
@type_params = type_params
|
|
387
|
+
@superclass = superclass
|
|
388
|
+
@_name = name
|
|
389
|
+
end
|
|
390
|
+
|
|
391
|
+
attr_reader :kind, :idx, :type_params, :superclass
|
|
392
|
+
|
|
393
|
+
def inspect
|
|
394
|
+
if @_name
|
|
395
|
+
"#{ @_name }@#{ @idx }"
|
|
396
|
+
else
|
|
397
|
+
"Class[#{ @idx }]"
|
|
398
|
+
end
|
|
399
|
+
end
|
|
400
|
+
|
|
401
|
+
def screen_name(scratch)
|
|
402
|
+
"#{ scratch.get_class_name(self) }.class"
|
|
403
|
+
end
|
|
404
|
+
|
|
405
|
+
def get_method(mid, scratch)
|
|
406
|
+
scratch.get_method(self, true, mid)
|
|
407
|
+
end
|
|
408
|
+
|
|
409
|
+
def consistent?(other, subst)
|
|
410
|
+
case other
|
|
411
|
+
when Type::Any then true
|
|
412
|
+
when Type::Var then other.add_subst!(self, subst)
|
|
413
|
+
when Type::Union
|
|
414
|
+
other.types.each do |ty|
|
|
415
|
+
return true if consistent?(ty, subst)
|
|
416
|
+
end
|
|
417
|
+
return false
|
|
418
|
+
when Type::Class
|
|
419
|
+
ty = self
|
|
420
|
+
loop do
|
|
421
|
+
# ad-hoc
|
|
422
|
+
return false if !ty || !other # module
|
|
423
|
+
|
|
424
|
+
return true if ty.idx == other.idx
|
|
425
|
+
return false if ty.idx == 0 # Object
|
|
426
|
+
ty = ty.superclass
|
|
427
|
+
end
|
|
428
|
+
when Type::Instance
|
|
429
|
+
return true if other.klass == Type::Builtin[:obj] || other.klass == Type::Builtin[:class] || other.klass == Type::Builtin[:module]
|
|
430
|
+
return false
|
|
431
|
+
else
|
|
432
|
+
false
|
|
433
|
+
end
|
|
434
|
+
end
|
|
435
|
+
|
|
436
|
+
def substitute(_subst, _depth)
|
|
437
|
+
self
|
|
438
|
+
end
|
|
439
|
+
end
|
|
440
|
+
|
|
441
|
+
class Instance < Type
|
|
442
|
+
def initialize(klass)
|
|
443
|
+
raise unless klass
|
|
444
|
+
raise if klass == Type.any
|
|
445
|
+
@klass = klass
|
|
446
|
+
end
|
|
447
|
+
|
|
448
|
+
attr_reader :klass
|
|
449
|
+
|
|
450
|
+
def inspect
|
|
451
|
+
"I[#{ @klass.inspect }]"
|
|
452
|
+
end
|
|
453
|
+
|
|
454
|
+
def screen_name(scratch)
|
|
455
|
+
case @klass
|
|
456
|
+
when Type::Builtin[:nil] then "nil"
|
|
457
|
+
when Type::Builtin[:true] then "true"
|
|
458
|
+
when Type::Builtin[:false] then "false"
|
|
459
|
+
else
|
|
460
|
+
scratch.get_class_name(@klass)
|
|
461
|
+
end
|
|
462
|
+
end
|
|
463
|
+
|
|
464
|
+
def get_method(mid, scratch)
|
|
465
|
+
scratch.get_method(@klass, false, mid)
|
|
466
|
+
end
|
|
467
|
+
|
|
468
|
+
def consistent?(other, subst)
|
|
469
|
+
case other
|
|
470
|
+
when Type::Any then true
|
|
471
|
+
when Type::Var then other.add_subst!(self, subst)
|
|
472
|
+
when Type::Union
|
|
473
|
+
other.types.each do |ty|
|
|
474
|
+
return true if consistent?(ty, subst)
|
|
475
|
+
end
|
|
476
|
+
return false
|
|
477
|
+
when Type::Instance
|
|
478
|
+
@klass.consistent?(other.klass, subst)
|
|
479
|
+
when Type::Class
|
|
480
|
+
return true if @klass == Type::Builtin[:obj] || @klass == Type::Builtin[:class] || @klass == Type::Builtin[:module]
|
|
481
|
+
return false
|
|
482
|
+
else
|
|
483
|
+
false
|
|
484
|
+
end
|
|
485
|
+
end
|
|
486
|
+
|
|
487
|
+
def substitute(subst, depth)
|
|
488
|
+
Instance.new(@klass.substitute(subst, depth))
|
|
489
|
+
end
|
|
490
|
+
end
|
|
491
|
+
|
|
492
|
+
class ISeq < Type
|
|
493
|
+
def initialize(iseq)
|
|
494
|
+
@iseq = iseq
|
|
495
|
+
end
|
|
496
|
+
|
|
497
|
+
attr_reader :iseq
|
|
498
|
+
|
|
499
|
+
def inspect
|
|
500
|
+
"Type::ISeq[#{ @iseq }]"
|
|
501
|
+
end
|
|
502
|
+
|
|
503
|
+
def screen_name(_scratch)
|
|
504
|
+
raise NotImplementedError
|
|
505
|
+
end
|
|
506
|
+
end
|
|
507
|
+
|
|
508
|
+
class ISeqProc < Type
|
|
509
|
+
def initialize(iseq, ep, type)
|
|
510
|
+
@iseq = iseq
|
|
511
|
+
@ep = ep
|
|
512
|
+
@type = type
|
|
513
|
+
end
|
|
514
|
+
|
|
515
|
+
attr_reader :iseq, :ep, :type
|
|
516
|
+
|
|
517
|
+
def inspect
|
|
518
|
+
"#<ISeqProc>"
|
|
519
|
+
end
|
|
520
|
+
|
|
521
|
+
def screen_name(scratch)
|
|
522
|
+
"Proc[#{ scratch.proc_screen_name(self) }]" # TODO: use RBS syntax
|
|
523
|
+
end
|
|
524
|
+
|
|
525
|
+
def get_method(mid, scratch)
|
|
526
|
+
@type.get_method(mid, scratch)
|
|
527
|
+
end
|
|
528
|
+
|
|
529
|
+
def substitute(_subst, _depth)
|
|
530
|
+
self # XXX
|
|
531
|
+
end
|
|
532
|
+
end
|
|
533
|
+
|
|
534
|
+
class TypedProc < Type
|
|
535
|
+
def initialize(fargs, ret_ty, type)
|
|
536
|
+
# XXX: need to receive blk_ty?
|
|
537
|
+
# XXX: may refactor "arguments = arg_tys * blk_ty" out
|
|
538
|
+
@fargs = fargs
|
|
539
|
+
@ret_ty = ret_ty
|
|
540
|
+
@type = type
|
|
541
|
+
end
|
|
542
|
+
|
|
543
|
+
attr_reader :fargs, :ret_ty
|
|
544
|
+
|
|
545
|
+
def screen_name(scratch)
|
|
546
|
+
"TypedProc[...]" # TODO: use RBS syntax
|
|
547
|
+
end
|
|
548
|
+
end
|
|
549
|
+
|
|
550
|
+
class Symbol < Type
|
|
551
|
+
def initialize(sym, type)
|
|
552
|
+
@sym = sym
|
|
553
|
+
@type = type
|
|
554
|
+
end
|
|
555
|
+
|
|
556
|
+
attr_reader :sym, :type
|
|
557
|
+
|
|
558
|
+
def inspect
|
|
559
|
+
"Type::Symbol[#{ @sym ? @sym.inspect : "(dynamic symbol)" }, #{ @type.inspect }]"
|
|
560
|
+
end
|
|
561
|
+
|
|
562
|
+
def consistent?(other, subst)
|
|
563
|
+
case other
|
|
564
|
+
when Var
|
|
565
|
+
other.add_subst!(self, subst)
|
|
566
|
+
when Symbol
|
|
567
|
+
@sym == other.sym
|
|
568
|
+
else
|
|
569
|
+
@type.consistent?(other, subst)
|
|
570
|
+
end
|
|
571
|
+
end
|
|
572
|
+
|
|
573
|
+
def screen_name(scratch)
|
|
574
|
+
if @sym
|
|
575
|
+
@sym.inspect
|
|
576
|
+
else
|
|
577
|
+
@type.screen_name(scratch)
|
|
578
|
+
end
|
|
579
|
+
end
|
|
580
|
+
|
|
581
|
+
def get_method(mid, scratch)
|
|
582
|
+
@type.get_method(mid, scratch)
|
|
583
|
+
end
|
|
584
|
+
|
|
585
|
+
def substitute(_subst, _depth)
|
|
586
|
+
self # dummy
|
|
587
|
+
end
|
|
588
|
+
end
|
|
589
|
+
|
|
590
|
+
# local info
|
|
591
|
+
class Literal < Type
|
|
592
|
+
def initialize(lit, type)
|
|
593
|
+
@lit = lit
|
|
594
|
+
@type = type
|
|
595
|
+
end
|
|
596
|
+
|
|
597
|
+
attr_reader :lit, :type
|
|
598
|
+
|
|
599
|
+
def inspect
|
|
600
|
+
"Type::Literal[#{ @lit.inspect }, #{ @type.inspect }]"
|
|
601
|
+
end
|
|
602
|
+
|
|
603
|
+
def screen_name(scratch)
|
|
604
|
+
@type.screen_name(scratch) + "<#{ @lit.inspect }>"
|
|
605
|
+
end
|
|
606
|
+
|
|
607
|
+
def globalize(_env, _visited, _depth)
|
|
608
|
+
@type
|
|
609
|
+
end
|
|
610
|
+
|
|
611
|
+
def get_method(mid, scratch)
|
|
612
|
+
@type.get_method(mid, scratch)
|
|
613
|
+
end
|
|
614
|
+
|
|
615
|
+
def consistent?(other, subst)
|
|
616
|
+
@type.consistent?(other, subst)
|
|
617
|
+
end
|
|
618
|
+
end
|
|
619
|
+
|
|
620
|
+
class HashGenerator
|
|
621
|
+
def initialize
|
|
622
|
+
@map_tys = {}
|
|
623
|
+
end
|
|
624
|
+
|
|
625
|
+
attr_reader :map_tys
|
|
626
|
+
|
|
627
|
+
def []=(k_ty, v_ty)
|
|
628
|
+
k_ty.each_child_global do |k_ty|
|
|
629
|
+
# This is a temporal hack to mitigate type explosion
|
|
630
|
+
k_ty = Type.any if k_ty.is_a?(Type::Array)
|
|
631
|
+
k_ty = Type.any if k_ty.is_a?(Type::Hash)
|
|
632
|
+
|
|
633
|
+
if @map_tys[k_ty]
|
|
634
|
+
@map_tys[k_ty] = @map_tys[k_ty].union(v_ty)
|
|
635
|
+
else
|
|
636
|
+
@map_tys[k_ty] = v_ty
|
|
637
|
+
end
|
|
638
|
+
end
|
|
639
|
+
end
|
|
640
|
+
end
|
|
641
|
+
|
|
642
|
+
def self.gen_hash
|
|
643
|
+
hg = HashGenerator.new
|
|
644
|
+
yield hg
|
|
645
|
+
base_ty = Type::Instance.new(Type::Builtin[:hash])
|
|
646
|
+
Type::Hash.new(Type::Hash::Elements.new(hg.map_tys), base_ty)
|
|
647
|
+
end
|
|
648
|
+
|
|
649
|
+
def self.guess_literal_type(obj)
|
|
650
|
+
case obj
|
|
651
|
+
when ::Symbol
|
|
652
|
+
Type::Symbol.new(obj, Type::Instance.new(Type::Builtin[:sym]))
|
|
653
|
+
when ::Integer
|
|
654
|
+
Type::Literal.new(obj, Type::Instance.new(Type::Builtin[:int]))
|
|
655
|
+
when ::Rational
|
|
656
|
+
Type::Literal.new(obj, Type::Instance.new(Type::Builtin[:rational]))
|
|
657
|
+
when ::Float
|
|
658
|
+
Type::Literal.new(obj, Type::Instance.new(Type::Builtin[:float]))
|
|
659
|
+
when ::Class
|
|
660
|
+
return Type.any if obj < Exception
|
|
661
|
+
case obj
|
|
662
|
+
when ::Object
|
|
663
|
+
Type::Builtin[:obj]
|
|
664
|
+
when ::Array
|
|
665
|
+
Type::Builtin[:ary]
|
|
666
|
+
else
|
|
667
|
+
raise "unknown class: #{ obj.inspect }"
|
|
668
|
+
end
|
|
669
|
+
when ::TrueClass
|
|
670
|
+
Type::Instance.new(Type::Builtin[:true])
|
|
671
|
+
when ::FalseClass
|
|
672
|
+
Type::Instance.new(Type::Builtin[:false])
|
|
673
|
+
when ::Array
|
|
674
|
+
base_ty = Type::Instance.new(Type::Builtin[:ary])
|
|
675
|
+
lead_tys = obj.map {|arg| guess_literal_type(arg) }
|
|
676
|
+
Type::Array.new(Type::Array::Elements.new(lead_tys), base_ty)
|
|
677
|
+
when ::Hash
|
|
678
|
+
Type.gen_hash do |h|
|
|
679
|
+
obj.each do |k, v|
|
|
680
|
+
k_ty = guess_literal_type(k).globalize(nil, {}, Config.options[:type_depth_limit])
|
|
681
|
+
v_ty = guess_literal_type(v)
|
|
682
|
+
h[k_ty] = v_ty
|
|
683
|
+
end
|
|
684
|
+
end
|
|
685
|
+
when ::String
|
|
686
|
+
Type::Literal.new(obj, Type::Instance.new(Type::Builtin[:str]))
|
|
687
|
+
when ::Regexp
|
|
688
|
+
Type::Literal.new(obj, Type::Instance.new(Type::Builtin[:regexp]))
|
|
689
|
+
when ::NilClass
|
|
690
|
+
Type.nil
|
|
691
|
+
when ::Range
|
|
692
|
+
Type::Literal.new(obj, Type::Instance.new(Type::Builtin[:range]))
|
|
693
|
+
else
|
|
694
|
+
raise "unknown object: #{ obj.inspect }"
|
|
695
|
+
end
|
|
696
|
+
end
|
|
697
|
+
|
|
698
|
+
def self.builtin_global_variable_type(var)
|
|
699
|
+
case var
|
|
700
|
+
when :$_, :$/, :$\, :$,, :$;
|
|
701
|
+
Type.optional(Type::Instance.new(Type::Builtin[:str]))
|
|
702
|
+
when :$0, :$PROGRAM_NAME
|
|
703
|
+
Type::Instance.new(Type::Builtin[:str])
|
|
704
|
+
when :$~
|
|
705
|
+
Type.optional(Type::Instance.new(Type::Builtin[:matchdata]))
|
|
706
|
+
when :$., :$$
|
|
707
|
+
Type::Instance.new(Type::Builtin[:int])
|
|
708
|
+
when :$?
|
|
709
|
+
Type.optional(Type::Instance.new(Type::Builtin[:int]))
|
|
710
|
+
when :$!
|
|
711
|
+
Type.optional(Type::Instance.new(Type::Builtin[:exc]))
|
|
712
|
+
when :$@
|
|
713
|
+
str = Type::Instance.new(Type::Builtin[:str])
|
|
714
|
+
base_ty = Type::Instance.new(Type::Builtin[:ary])
|
|
715
|
+
Type.optional(Type::Array.new(Type::Array::Elements.new([], str), base_ty))
|
|
716
|
+
when :$*, :$:, :$LOAD_PATH, :$", :$LOADED_FEATURES
|
|
717
|
+
str = Type::Instance.new(Type::Builtin[:str])
|
|
718
|
+
base_ty = Type::Instance.new(Type::Builtin[:ary])
|
|
719
|
+
Type::Array.new(Type::Array::Elements.new([], str), base_ty)
|
|
720
|
+
when :$<
|
|
721
|
+
:ARGF
|
|
722
|
+
when :$>
|
|
723
|
+
:STDOUT
|
|
724
|
+
when :$DEBUG
|
|
725
|
+
Type.bool
|
|
726
|
+
when :$FILENAME
|
|
727
|
+
Type::Instance.new(Type::Builtin[:str])
|
|
728
|
+
when :$stdin
|
|
729
|
+
:STDIN
|
|
730
|
+
when :$stdout
|
|
731
|
+
:STDOUT
|
|
732
|
+
when :$stderr
|
|
733
|
+
:STDERR
|
|
734
|
+
when :$VERBOSE
|
|
735
|
+
Type.bool.union(Type.nil)
|
|
736
|
+
else
|
|
737
|
+
nil
|
|
738
|
+
end
|
|
739
|
+
end
|
|
740
|
+
end
|
|
741
|
+
|
|
742
|
+
# Arguments for callee side
|
|
743
|
+
class FormalArguments
|
|
744
|
+
include Utils::StructuralEquality
|
|
745
|
+
|
|
746
|
+
def initialize(lead_tys, opt_tys, rest_ty, post_tys, kw_tys, kw_rest_ty, blk_ty)
|
|
747
|
+
@lead_tys = lead_tys
|
|
748
|
+
@opt_tys = opt_tys
|
|
749
|
+
@rest_ty = rest_ty
|
|
750
|
+
@post_tys = post_tys
|
|
751
|
+
@kw_tys = kw_tys
|
|
752
|
+
kw_tys.each {|a| raise if a.size != 3 } if kw_tys
|
|
753
|
+
@kw_rest_ty = kw_rest_ty
|
|
754
|
+
@blk_ty = blk_ty
|
|
755
|
+
end
|
|
756
|
+
|
|
757
|
+
attr_reader :lead_tys, :opt_tys, :rest_ty, :post_tys, :kw_tys, :kw_rest_ty, :blk_ty
|
|
758
|
+
|
|
759
|
+
def consistent?(fargs, subst)
|
|
760
|
+
warn "used?"
|
|
761
|
+
return false if @lead_tys.size != fargs.lead_tys.size
|
|
762
|
+
return false unless @lead_tys.zip(fargs.lead_tys).all? {|ty1, ty2| ty1.consistent?(ty2, subst) }
|
|
763
|
+
return false if (@opt_tys || []) != (fargs.opt_tys || []) # ??
|
|
764
|
+
if @rest_ty
|
|
765
|
+
return false unless @rest_ty.consistent?(fargs.rest_ty, subst)
|
|
766
|
+
end
|
|
767
|
+
if @post_tys
|
|
768
|
+
return false if @post_tys.size != fargs.post_tys.size
|
|
769
|
+
return false unless @post_tys.zip(fargs.post_tys).all? {|ty1, ty2| ty1.consistent?(ty2, subst) }
|
|
770
|
+
end
|
|
771
|
+
return false if @kw_tys.size != fargs.kw_tys.size
|
|
772
|
+
return false unless @kw_tys.zip(fargs.kw_tys).all? {|(_, ty1), (_, ty2)| ty1.consistent?(ty2, subst) }
|
|
773
|
+
if @kw_rest_ty
|
|
774
|
+
return false unless @kw_rest_ty.consistent?(fargs.kw_rest_ty, subst)
|
|
775
|
+
end
|
|
776
|
+
# intentionally skip blk_ty
|
|
777
|
+
true
|
|
778
|
+
end
|
|
779
|
+
|
|
780
|
+
def screen_name(scratch)
|
|
781
|
+
fargs = @lead_tys.map {|ty| ty.screen_name(scratch) }
|
|
782
|
+
if @opt_tys
|
|
783
|
+
fargs += @opt_tys.map {|ty| "?" + ty.screen_name(scratch) }
|
|
784
|
+
end
|
|
785
|
+
if @rest_ty
|
|
786
|
+
fargs << ("*" + @rest_ty.screen_name(scratch))
|
|
787
|
+
end
|
|
788
|
+
if @post_tys
|
|
789
|
+
fargs += @post_tys.map {|ty| ty.screen_name(scratch) }
|
|
790
|
+
end
|
|
791
|
+
if @kw_tys
|
|
792
|
+
@kw_tys.each do |req, sym, ty|
|
|
793
|
+
opt = req ? "" : "?"
|
|
794
|
+
fargs << "#{ opt }#{ sym }: #{ ty.screen_name(scratch) }"
|
|
795
|
+
end
|
|
796
|
+
end
|
|
797
|
+
if @kw_rest_ty
|
|
798
|
+
fargs << ("**" + @kw_rest_ty.screen_name(scratch))
|
|
799
|
+
end
|
|
800
|
+
# intentionally skip blk_ty
|
|
801
|
+
fargs
|
|
802
|
+
end
|
|
803
|
+
|
|
804
|
+
def merge(other)
|
|
805
|
+
raise if @lead_tys.size != other.lead_tys.size
|
|
806
|
+
raise if @post_tys.size != other.post_tys.size
|
|
807
|
+
if @kw_tys && other.kw_tys
|
|
808
|
+
kws1 = {}
|
|
809
|
+
@kw_tys.each {|req, kw, _| kws1[kw] = req }
|
|
810
|
+
kws2 = {}
|
|
811
|
+
other.kw_tys.each {|req, kw, _| kws2[kw] = req }
|
|
812
|
+
(kws1.keys & kws2.keys).each do |kw|
|
|
813
|
+
raise if !!kws1[kw] != !!kws2[kw]
|
|
814
|
+
end
|
|
815
|
+
elsif @kw_tys || other.kw_tys
|
|
816
|
+
puts
|
|
817
|
+
p self, other
|
|
818
|
+
(@kw_tys || other.kw_tys).each do |req,|
|
|
819
|
+
raise if req
|
|
820
|
+
end
|
|
821
|
+
end
|
|
822
|
+
lead_tys = @lead_tys.zip(other.lead_tys).map {|ty1, ty2| ty1.union(ty2) }
|
|
823
|
+
if @opt_tys || other.opt_tys
|
|
824
|
+
opt_tys = []
|
|
825
|
+
[@opt_tys.size, other.opt_tys.size].max.times do |i|
|
|
826
|
+
ty1 = @opt_tys[i]
|
|
827
|
+
ty2 = other.opt_tys[i]
|
|
828
|
+
ty = ty1 ? ty2 ? ty1.union(ty2) : ty1 : ty2
|
|
829
|
+
opt_tys << ty
|
|
830
|
+
end
|
|
831
|
+
end
|
|
832
|
+
if @rest_ty || other.rest_ty
|
|
833
|
+
if @rest_ty && other.rest_ty
|
|
834
|
+
rest_ty = @rest_ty.union(other.rest_ty)
|
|
835
|
+
else
|
|
836
|
+
rest_ty = @rest_ty || other.rest_ty
|
|
837
|
+
end
|
|
838
|
+
end
|
|
839
|
+
post_tys = @post_tys.zip(other.post_tys).map {|ty1, ty2| ty1.union(ty2) }
|
|
840
|
+
if @kw_tys && other.kw_tys
|
|
841
|
+
kws1 = {}
|
|
842
|
+
@kw_tys.each {|req, kw, ty| kws1[kw] = [req, ty] }
|
|
843
|
+
kws2 = {}
|
|
844
|
+
other.kw_tys.each {|req, kw, ty| kws2[kw] = [req, ty] }
|
|
845
|
+
kw_tys = (kws1.keys | kws2.keys).map do |kw|
|
|
846
|
+
req1, ty1 = kws1[kw]
|
|
847
|
+
_req2, ty2 = kws2[kw]
|
|
848
|
+
ty1 ||= Type.bot
|
|
849
|
+
ty2 ||= Type.bot
|
|
850
|
+
[!!req1, kw, ty1.union(ty2)]
|
|
851
|
+
end
|
|
852
|
+
elsif @kw_tys || other.kw_tys
|
|
853
|
+
kw_tys = @kw_tys || other.kw_tys
|
|
854
|
+
else
|
|
855
|
+
kw_tys = nil
|
|
856
|
+
end
|
|
857
|
+
if @kw_rest_ty || other.kw_rest_ty
|
|
858
|
+
if @kw_rest_ty && other.kw_rest_ty
|
|
859
|
+
kw_rest_ty = @kw_rest_ty.union(other.kw_rest_ty)
|
|
860
|
+
else
|
|
861
|
+
kw_rest_ty = @kw_rest_ty || other.kw_rest_ty
|
|
862
|
+
end
|
|
863
|
+
end
|
|
864
|
+
blk_ty = @blk_ty.union(other.blk_ty) if @blk_ty
|
|
865
|
+
FormalArguments.new(lead_tys, opt_tys, rest_ty, post_tys, kw_tys, kw_rest_ty, blk_ty)
|
|
866
|
+
end
|
|
867
|
+
end
|
|
868
|
+
|
|
869
|
+
# Arguments from caller side
|
|
870
|
+
class ActualArguments
|
|
871
|
+
def initialize(lead_tys, rest_ty, kw_ty, blk_ty)
|
|
872
|
+
@lead_tys = lead_tys
|
|
873
|
+
@rest_ty = rest_ty
|
|
874
|
+
@kw_ty = kw_ty
|
|
875
|
+
@blk_ty = blk_ty
|
|
876
|
+
raise unless blk_ty
|
|
877
|
+
end
|
|
878
|
+
|
|
879
|
+
attr_reader :lead_tys, :rest_ty, :kw_ty, :blk_ty
|
|
880
|
+
|
|
881
|
+
def merge(aargs)
|
|
882
|
+
len = [@lead_tys.size, aargs.lead_tys.size].min
|
|
883
|
+
lead_tys = @lead_tys[0, len].zip(aargs.lead_tys[0, len]).map do |ty1, ty2|
|
|
884
|
+
ty1.union(ty2)
|
|
885
|
+
end
|
|
886
|
+
rest_ty = @rest_ty || Type.bot
|
|
887
|
+
rest_ty = rest_ty.union(aargs.rest_ty) if aargs.rest_ty
|
|
888
|
+
(@lead_tys[len..] + aargs.lead_tys[len..]).each do |ty|
|
|
889
|
+
rest_ty = rest_ty.union(ty)
|
|
890
|
+
end
|
|
891
|
+
rest_ty = nil if rest_ty == Type.bot
|
|
892
|
+
#kw_ty = @kw_ty.union(aargs.kw_ty) # TODO
|
|
893
|
+
blk_ty = @blk_ty.union(aargs.blk_ty)
|
|
894
|
+
ActualArguments.new(lead_tys, rest_ty, kw_ty, blk_ty)
|
|
895
|
+
end
|
|
896
|
+
|
|
897
|
+
def globalize(caller_env, visited, depth)
|
|
898
|
+
lead_tys = @lead_tys.map {|ty| ty.globalize(caller_env, visited, depth) }
|
|
899
|
+
rest_ty = @rest_ty.globalize(caller_env, visited, depth) if @rest_ty
|
|
900
|
+
kw_ty = @kw_ty.globalize(caller_env, visited, depth) if @kw_ty
|
|
901
|
+
ActualArguments.new(lead_tys, rest_ty, kw_ty, @blk_ty)
|
|
902
|
+
end
|
|
903
|
+
|
|
904
|
+
def limit_size(limit)
|
|
905
|
+
self
|
|
906
|
+
end
|
|
907
|
+
|
|
908
|
+
def each_formal_arguments(fargs_format)
|
|
909
|
+
lead_num = fargs_format[:lead_num] || 0
|
|
910
|
+
post_num = fargs_format[:post_num] || 0
|
|
911
|
+
rest_acceptable = !!fargs_format[:rest_start]
|
|
912
|
+
keyword = fargs_format[:keyword]
|
|
913
|
+
kw_rest_acceptable = !!fargs_format[:kwrest]
|
|
914
|
+
opt = fargs_format[:opt]
|
|
915
|
+
#p fargs_format
|
|
916
|
+
|
|
917
|
+
# TODO: expand tuples to normal arguments
|
|
918
|
+
|
|
919
|
+
# check number of arguments
|
|
920
|
+
if !@rest_ty && lead_num + post_num > @lead_tys.size
|
|
921
|
+
# too less
|
|
922
|
+
yield "wrong number of arguments (given #{ @lead_tys.size }, expected #{ lead_num + post_num })"
|
|
923
|
+
return
|
|
924
|
+
end
|
|
925
|
+
if !rest_acceptable
|
|
926
|
+
# too many
|
|
927
|
+
if opt
|
|
928
|
+
if lead_num + post_num + opt.size - 1 < @lead_tys.size
|
|
929
|
+
yield "wrong number of arguments (given #{ @lead_tys.size }, expected #{ lead_num + post_num }..#{ lead_num + post_num + opt.size - 1})"
|
|
930
|
+
return
|
|
931
|
+
end
|
|
932
|
+
else
|
|
933
|
+
if lead_num + post_num < @lead_tys.size
|
|
934
|
+
yield "wrong number of arguments (given #{ @lead_tys.size }, expected #{ lead_num + post_num })"
|
|
935
|
+
return
|
|
936
|
+
end
|
|
937
|
+
end
|
|
938
|
+
end
|
|
939
|
+
|
|
940
|
+
if @rest_ty
|
|
941
|
+
lower_bound = [lead_num + post_num - @lead_tys.size, 0].max
|
|
942
|
+
upper_bound = lead_num + post_num - @lead_tys.size + (opt ? opt.size - 1 : 0) + (rest_acceptable ? 1 : 0)
|
|
943
|
+
rest_elem = @rest_ty.is_a?(Type::Array) ? @rest_ty.elems.squash : Type.any
|
|
944
|
+
else
|
|
945
|
+
lower_bound = upper_bound = 0
|
|
946
|
+
end
|
|
947
|
+
|
|
948
|
+
if keyword
|
|
949
|
+
kw_tys = []
|
|
950
|
+
keyword.each do |kw|
|
|
951
|
+
case
|
|
952
|
+
when kw.is_a?(Symbol) # required keyword
|
|
953
|
+
key = kw
|
|
954
|
+
req = true
|
|
955
|
+
when kw.size == 2 # optional keyword (default value is a literal)
|
|
956
|
+
key, default_ty = *kw
|
|
957
|
+
default_ty = Type.guess_literal_type(default_ty)
|
|
958
|
+
default_ty = default_ty.type if default_ty.is_a?(Type::Literal)
|
|
959
|
+
req = false
|
|
960
|
+
else # optional keyword (default value is an expression)
|
|
961
|
+
key, = kw
|
|
962
|
+
req = false
|
|
963
|
+
end
|
|
964
|
+
|
|
965
|
+
sym = Type::Symbol.new(key, Type::Instance.new(Type::Builtin[:sym]))
|
|
966
|
+
ty = Type.bot
|
|
967
|
+
if @kw_ty.is_a?(Type::Hash)
|
|
968
|
+
# XXX: consider Union
|
|
969
|
+
ty = @kw_ty.elems[sym]
|
|
970
|
+
# XXX: remove the key
|
|
971
|
+
end
|
|
972
|
+
if ty == Type.bot
|
|
973
|
+
yield "no argument for required keywords"
|
|
974
|
+
return
|
|
975
|
+
end
|
|
976
|
+
ty = ty.union(default_ty) if default_ty
|
|
977
|
+
kw_tys << [req, key, ty]
|
|
978
|
+
end
|
|
979
|
+
end
|
|
980
|
+
if kw_rest_acceptable
|
|
981
|
+
kw_rest_ty = @kw_ty
|
|
982
|
+
if kw_rest_ty == Type.any
|
|
983
|
+
kw_rest_ty = Type.gen_hash {|h| h[Type.any] = Type.any }
|
|
984
|
+
end
|
|
985
|
+
end
|
|
986
|
+
#if @kw_ty
|
|
987
|
+
# yield "passed a keyword to non-keyword method"
|
|
988
|
+
#end
|
|
989
|
+
|
|
990
|
+
(lower_bound .. upper_bound).each do |rest_len|
|
|
991
|
+
aargs = @lead_tys + [rest_elem] * rest_len
|
|
992
|
+
lead_tys = aargs.shift(lead_num)
|
|
993
|
+
lead_tys << rest_elem until lead_tys.size == lead_num
|
|
994
|
+
post_tys = aargs.pop(post_num)
|
|
995
|
+
post_tys.unshift(rest_elem) until post_tys.size == post_num
|
|
996
|
+
start_pc = 0
|
|
997
|
+
if opt
|
|
998
|
+
tmp_opt = opt[1..]
|
|
999
|
+
opt_tys = []
|
|
1000
|
+
until aargs.empty? || tmp_opt.empty?
|
|
1001
|
+
opt_tys << aargs.shift
|
|
1002
|
+
start_pc = tmp_opt.shift
|
|
1003
|
+
end
|
|
1004
|
+
end
|
|
1005
|
+
if rest_acceptable
|
|
1006
|
+
acc = aargs.inject {|acc, ty| acc.union(ty) }
|
|
1007
|
+
acc = acc ? acc.union(rest_elem) : rest_elem if rest_elem
|
|
1008
|
+
acc ||= Type.bot
|
|
1009
|
+
rest_ty = acc
|
|
1010
|
+
aargs.clear
|
|
1011
|
+
end
|
|
1012
|
+
if !aargs.empty?
|
|
1013
|
+
yield "wrong number of arguments (given #{ @lead_tys.size }, expected #{ lead_num + post_num })"
|
|
1014
|
+
return
|
|
1015
|
+
end
|
|
1016
|
+
yield FormalArguments.new(lead_tys, opt_tys, rest_ty, post_tys, kw_tys, kw_rest_ty, @blk_ty), start_pc
|
|
1017
|
+
end
|
|
1018
|
+
end
|
|
1019
|
+
|
|
1020
|
+
def consistent_with_formal_arguments?(fargs, subst)
|
|
1021
|
+
aargs = @lead_tys.dup
|
|
1022
|
+
|
|
1023
|
+
# aargs: lead_tys, rest_ty
|
|
1024
|
+
# fargs: lead_tys, opt_tys, rest_ty, post_tys
|
|
1025
|
+
if @rest_ty
|
|
1026
|
+
lower_bound = [0, fargs.lead_tys.size + fargs.post_tys.size - aargs.size].max
|
|
1027
|
+
upper_bound = [0, lower_bound + fargs.opt_tys.size].max
|
|
1028
|
+
(lower_bound..upper_bound).each do |n|
|
|
1029
|
+
tmp_aargs = ActualArguments.new(@lead_tys + [@rest_ty] * n, nil, @kw_ty, @blk_ty)
|
|
1030
|
+
if tmp_aargs.consistent_with_formal_arguments?(fargs, subst)
|
|
1031
|
+
return true
|
|
1032
|
+
end
|
|
1033
|
+
end
|
|
1034
|
+
return false
|
|
1035
|
+
end
|
|
1036
|
+
|
|
1037
|
+
if fargs.rest_ty
|
|
1038
|
+
return false if aargs.size < fargs.lead_tys.size + fargs.post_tys.size
|
|
1039
|
+
aargs.shift(fargs.lead_tys.size).zip(fargs.lead_tys) do |aarg, farg|
|
|
1040
|
+
return false unless aarg.consistent?(farg, subst)
|
|
1041
|
+
end
|
|
1042
|
+
aargs.pop(fargs.post_tys.size).zip(fargs.post_tys) do |aarg, farg|
|
|
1043
|
+
return false unless aarg.consistent?(farg, subst)
|
|
1044
|
+
end
|
|
1045
|
+
fargs.opt_tys.each do |farg|
|
|
1046
|
+
aarg = aargs.shift
|
|
1047
|
+
return false unless aarg.consistent?(farg, subst)
|
|
1048
|
+
end
|
|
1049
|
+
aargs.each do |aarg|
|
|
1050
|
+
return false unless aarg.consistent?(fargs.rest_ty, subst)
|
|
1051
|
+
end
|
|
1052
|
+
else
|
|
1053
|
+
return false if aargs.size < fargs.lead_tys.size + fargs.post_tys.size
|
|
1054
|
+
return false if aargs.size > fargs.lead_tys.size + fargs.post_tys.size + fargs.opt_tys.size
|
|
1055
|
+
aargs.shift(fargs.lead_tys.size).zip(fargs.lead_tys) do |aarg, farg|
|
|
1056
|
+
return false unless aarg.consistent?(farg, subst)
|
|
1057
|
+
end
|
|
1058
|
+
aargs.pop(fargs.post_tys.size).zip(fargs.post_tys) do |aarg, farg|
|
|
1059
|
+
return false unless aarg.consistent?(farg, subst)
|
|
1060
|
+
end
|
|
1061
|
+
aargs.zip(fargs.opt_tys) do |aarg, farg|
|
|
1062
|
+
return false unless aarg.consistent?(farg, subst)
|
|
1063
|
+
end
|
|
1064
|
+
end
|
|
1065
|
+
# XXX: fargs.keyword_tys
|
|
1066
|
+
|
|
1067
|
+
case fargs.blk_ty
|
|
1068
|
+
when Type::TypedProc
|
|
1069
|
+
return false if @blk_ty == Type.nil
|
|
1070
|
+
when Type.nil
|
|
1071
|
+
return false if @blk_ty != Type.nil
|
|
1072
|
+
when Type::Any
|
|
1073
|
+
else
|
|
1074
|
+
raise "unknown typo of formal block signature"
|
|
1075
|
+
end
|
|
1076
|
+
true
|
|
1077
|
+
end
|
|
1078
|
+
|
|
1079
|
+
def screen_name(scratch)
|
|
1080
|
+
aargs = @lead_tys.map {|ty| ty.screen_name(scratch) }
|
|
1081
|
+
if @rest_ty
|
|
1082
|
+
aargs << ("*" + @rest_ty.screen_name(scratch))
|
|
1083
|
+
end
|
|
1084
|
+
if @kw_ty
|
|
1085
|
+
aargs << ("**" + @kw_ty.screen_name(scratch)) # TODO: Hash notation -> keyword notation
|
|
1086
|
+
end
|
|
1087
|
+
s = "(#{ aargs.join(", ") })"
|
|
1088
|
+
s << " { #{ scratch.proc_screen_name(@blk_ty) } }" if @blk_ty != Type.nil
|
|
1089
|
+
s
|
|
1090
|
+
end
|
|
1091
|
+
end
|
|
1092
|
+
end
|