-
Notifications
You must be signed in to change notification settings - Fork 83
/
arguments.rb
414 lines (372 loc) · 15.2 KB
/
arguments.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
module TypeProf
# Arguments from caller side
class ActualArguments
def initialize(lead_tys, rest_ty, kw_tys, blk_ty)
@lead_tys = lead_tys
raise unless lead_tys
@rest_ty = rest_ty
@kw_tys = kw_tys # kw_tys should be {:key1 => Type, :key2 => Type, ...} or {nil => Type}
raise if !kw_tys.is_a?(::Hash)
@blk_ty = blk_ty
raise unless blk_ty
end
def for_method_missing(mid)
ActualArguments.new([mid] + @lead_tys, @rest_ty, @kw_tys, @blk_ty)
end
attr_reader :lead_tys, :rest_ty, :kw_tys, :blk_ty
attr_accessor :node_id
def globalize(caller_env, visited, depth)
lead_tys = @lead_tys.map {|ty| ty.globalize(caller_env, visited, depth) }
rest_ty = @rest_ty.globalize(caller_env, visited, depth) if @rest_ty
kw_tys = @kw_tys.to_h do |key, ty|
[key, ty.globalize(caller_env, visited, depth)]
end
ActualArguments.new(lead_tys, rest_ty, kw_tys, @blk_ty)
end
def limit_size(limit)
self
end
def consistent_with_method_signature?(msig)
aargs = @lead_tys.dup
# aargs: lead_tys, rest_ty
# msig: lead_tys, opt_tys, rest_ty, post_tys
if @rest_ty
lower_bound = [0, msig.lead_tys.size + msig.post_tys.size - aargs.size].max
upper_bound = [0, lower_bound + msig.opt_tys.size].max
(lower_bound..upper_bound).each do |n|
# BUG: @rest_ty is an Array, so need to squash
tmp_aargs = ActualArguments.new(@lead_tys + [@rest_ty] * n, nil, @kw_tys, @blk_ty)
subst = tmp_aargs.consistent_with_method_signature?(msig) # XXX: wrong subst handling in the loop!
return subst if subst
end
return nil
end
subst = {}
if msig.rest_ty
return nil if aargs.size < msig.lead_tys.size + msig.post_tys.size
aargs.shift(msig.lead_tys.size).zip(msig.lead_tys) do |aarg, farg|
return nil unless subst2 = Type.match?(aarg, farg)
subst = Type.merge_substitution(subst, subst2)
end
aargs.pop(msig.post_tys.size).zip(msig.post_tys) do |aarg, farg|
return nil unless subst2 = Type.match?(aarg, farg)
subst = Type.merge_substitution(subst, subst2)
end
msig.opt_tys.each do |farg|
break if aargs.empty?
aarg = aargs.shift
return nil unless subst2 = Type.match?(aarg, farg)
subst = Type.merge_substitution(subst, subst2)
end
aargs.each do |aarg|
return nil unless subst2 = Type.match?(aarg, msig.rest_ty)
subst = Type.merge_substitution(subst, subst2)
end
else
return nil if aargs.size < msig.lead_tys.size + msig.post_tys.size
return nil if aargs.size > msig.lead_tys.size + msig.post_tys.size + msig.opt_tys.size
aargs.shift(msig.lead_tys.size).zip(msig.lead_tys) do |aarg, farg|
return nil unless subst2 = Type.match?(aarg, farg)
subst = Type.merge_substitution(subst, subst2)
end
aargs.pop(msig.post_tys.size).zip(msig.post_tys) do |aarg, farg|
return nil unless subst2 = Type.match?(aarg, farg)
subst = Type.merge_substitution(subst, subst2)
end
aargs.zip(msig.opt_tys) do |aarg, farg|
return nil unless subst2 = Type.match?(aarg, farg)
subst = Type.merge_substitution(subst, subst2)
end
end
# XXX: msig.keyword_tys
case msig.blk_ty
when Type::Proc
return nil if @blk_ty == Type.nil
when Type.nil
return nil if @blk_ty != Type.nil
when Type::Any
else
raise "unknown type of formal block signature"
end
subst
end
def argument_error(given, exp_lower, exp_upper)
case
when !exp_upper then exp = "#{ exp_lower }+"
when exp_lower == exp_upper then exp = "#{ exp_lower }"
else exp = "#{ exp_lower }..#{ exp_upper }"
end
"wrong number of arguments (given #{ given }, expected #{ exp })"
end
def to_block_signature
if @rest_ty
rest_ty = Type.bot
@rest_ty.each_child_global do |ty|
if ty.is_a?(Type::Array)
rest_ty = rest_ty.union(ty.elems.squash)
else
# XXX: to_ary?
rest_ty = rest_ty.union(ty)
end
end
end
BlockSignature.new(@lead_tys, [], rest_ty, @blk_ty)
end
def setup_formal_arguments(kind, locals, fargs_format)
lead_num = fargs_format[:lead_num] || 0
post_num = fargs_format[:post_num] || 0
post_index = fargs_format[:post_start]
rest_index = fargs_format[:rest_start]
keyword = fargs_format[:keyword]
kw_index = fargs_format[:kwbits] - keyword.size if keyword
kwrest_index = fargs_format[:kwrest]
block_index = fargs_format[:block_start]
opt = fargs_format[:opt] || [0]
ambiguous_param0 = fargs_format[:ambiguous_param0]
lead_tys = @lead_tys
rest_ty = @rest_ty
if kind == :block
# The rule of passing arguments to block:
#
# Let A is actual arguments and F is formal arguments.
# If F is NOT ambiguous_param0, and if length(A) == 1, and if A[0] is an Array,
# then replace A with A[0]. And then, F[i] = A[i] for all 0 <= i < length(F).
# Handling the special case
if !ambiguous_param0
if lead_tys.size == 1 && !rest_ty && @kw_tys.empty? # length(A) == 1
ty = lead_tys[0]
case ty
when Type::Array
lead_tys = ty.elems.lead_tys
rest_ty = ty.elems.rest_ty
when Type::Union
if ty.elems
other_elems = {}
ty.elems.each do |(container_kind, base_type), elems|
if container_kind == Type::Array
rest_ty = rest_ty ? rest_ty.union(elems.squash) : elems.squash
else
other_elems[[container_kind, base_type]] = elems
end
end
end
lead_tys = [Type::Union.new(ty.types, other_elems)]
end
end
end
end
# Normal case: copy actual args to formal args
if rest_ty
ty = Type.bot
additional_lead_size = nil
rest_ty.each_child_global do |ty0|
if ty0.is_a?(Type::Array)
additional_lead_size = [additional_lead_size, ty0.elems.lead_tys.size].compact.min
else
additional_lead_size = 0
end
end
additional_lead_tys = [Type.bot] * (additional_lead_size || 0)
rest_ty.each_child_global do |ty0|
if ty0.is_a?(Type::Array)
tys, new_rest_ty = ty0.elems.take_first(additional_lead_size)
tys.each_with_index do |ty00, i|
additional_lead_tys[i] = additional_lead_tys[i].union(ty00)
end
ty = ty.union(new_rest_ty.elems.squash)
else
# XXX: to_ary?
ty = ty.union(ty0)
end
end
lead_tys += additional_lead_tys
rest_ty = ty
# XXX: Strictly speaking, this is needed, but it brings false positives. Which is better?
#rest_ty = rest_ty.union(Type.nil)
if rest_index
# foo(a0, a1, a2, ...(rest_ty)) -->
# def foo(l0, l1, o0=, o1=, *rest, p0, p1)
# lead_ty argc == 0: - - - - - - -
# lead_ty argc == 1: a0 - - - - - -
# lead_ty argc == 2: a0 a1 - - - - -
# lead_ty argc == 3: a0 a1 - - - a2 -
# lead_ty argc == 4: a0 a1 - - - a2 a3
# lead_ty argc == 5: a0 a1 a2 - - a3 a4
# lead_ty argc == 6: a0 a1 a2 a3 - a4 a5
# lead_ty argc == 7: a0 a1 a2 a3 a4 a5 a6
# lead_ty argc == 8: a0 a1 a2 a3 a4|a5 a6 a7
#
# l0 = a0
# l1 = a1
# o0 = a2
# o1 = a3
# rest = a4|a5|...|rest_ty (= cum_lead_tys[4])
# p0 = a2|a3|...|rest_ty (= cum_lead_tys[2])
# p1 = a3|a4|...|rest_ty (= cum_lead_tys[3])
cum_lead_tys = []
ty = rest_ty
lead_tys.reverse_each do |ty0|
cum_lead_tys.unshift(ty = ty.union(ty0))
end
# l1, l2, o1, o2
(lead_num + opt.size - 1).times {|i| locals[i] = lead_tys[i] || rest_ty }
opt_count = opt.size - 1
# rest
ty = cum_lead_tys[lead_num + opt.size - 1] || rest_ty
locals[rest_index] = Type::Array.new(Type::Array::Elements.new([], ty), Type::Instance.new(Type::Builtin[:ary]))
# p0, p1
off = [lead_num, lead_tys.size - post_num].max
post_num.times {|i| locals[post_index + i] = cum_lead_tys[off + i] || rest_ty }
else
# foo(a0, a1, a2, ...(rest_ty)) -->
# def foo(l0, l1, o0=, o1=, p0, p1)
# lead_ty argc == 0: - - - - - -
# lead_ty argc == 1: a0 - - - - -
# lead_ty argc == 2: a0 a1 - - - -
# lead_ty argc == 3: a0 a1 - - a2 -
# lead_ty argc == 4: a0 a1 - - a2 a3
# lead_ty argc == 5: a0 a1 a2 - a3 a4
# lead_ty argc == 6: a0 a1 a2 a3 a4 a5
# lead_ty argc == 7: a0 a1 a2 a3 a4 a5 (if there is a6, report error if kind is method, or ignore if kind is block)
#
# l0 = a0
# l1 = a1
# o0 = a2
# o1 = a3
# p0 = a2|a3|a4
# p1 = a3|a4|a5
if kind == :method && lead_num + opt.size - 1 + post_num < lead_tys.size
return argument_error(lead_tys.size, lead_num + post_num, lead_num + post_num + opt.size - 1)
end
# l1, l2, o1, o2
(lead_num + opt.size - 1).times {|i| locals[i] = lead_tys[i] || rest_ty }
opt_count = opt.size - 1
# p0, p1
post_num.times do |i|
candidates = lead_tys[lead_num, opt.size] || []
candidates << rest_ty if candidates.size < opt.size
locals[post_index + i] = candidates.inject(&:union)
end
end
else
if rest_index
# foo(a0, a1, a2) -->
# def foo(l0, l1, o0=, o1=, *rest, p0, p1)
# lead_ty argc == 0: - - - - - - - (error if kind is method)
# lead_ty argc == 1: a0 - - - - - - (error if kind is method)
# lead_ty argc == 2: a0 a1 - - - - - (error if kind is method)
# lead_ty argc == 3: a0 a1 - - - a2 - (error if kind is method)
# lead_ty argc == 4: a0 a1 - - - a2 a3
# lead_ty argc == 5: a0 a1 a2 - - a3 a4
# lead_ty argc == 6: a0 a1 a2 a3 - a4 a5
# lead_ty argc == 7: a0 a1 a2 a3 a4 a5 a6
# lead_ty argc == 8: a0 a1 a2 a3 a4|a5 a6 a7
#
# len(a) < 4 -> error
#
# l0 = a0
# l1 = a1
# o0 = a2
# o1 = a3
# rest = a4|a5|...|a[[4,len(a)-3].max]
# p0 = a[[2,len(a)-2].max]
# p1 = a[[3,len(a)-1].max]
if kind == :method && lead_tys.size < lead_num + post_num
return argument_error(lead_tys.size, lead_num + post_num, nil)
end
# l0, l1
lead_num.times {|i| locals[i] = lead_tys[i] || Type.nil }
# o0, o1
opt_count = (lead_tys.size - lead_num - post_num).clamp(0, opt.size - 1)
(opt.size - 1).times {|i| locals[lead_num + i] = i < opt_count ? lead_tys[lead_num + i] : Type.nil }
# rest
rest_b = lead_num + opt_count
rest_e = [0, lead_tys.size - post_num].max
locals[rest_index] = Type::Array.new(Type::Array::Elements.new(lead_tys[rest_b...rest_e] || [], Type.bot), Type::Instance.new(Type::Builtin[:ary]))
# p0, p1
off = [lead_num, lead_tys.size - post_num].max
post_num.times {|i| locals[post_index + i] = lead_tys[off + i] || Type.nil }
else
# yield a0, a1, a2 -->
# do |l0, l1, o0=, o1=, p0, p1|
# lead_ty argc == 0: - - - - - - (error if kind is method)
# lead_ty argc == 1: a0 - - - - - (error if kind is method)
# lead_ty argc == 2: a0 a1 - - - - (error if kind is method)
# lead_ty argc == 3: a0 a1 - - a2 - (error if kind is method)
# lead_ty argc == 4: a0 a1 - - a2 a3
# lead_ty argc == 5: a0 a1 a2 - a3 a4
# lead_ty argc == 6: a0 a1 a2 a3 a4 a5
# lead_ty argc == 7: a0 a1 a2 a3 a4 a5 (if there is a6, report error if kind is method, or ignore if kind is block)
#
# l0 = a0
# l1 = a1
# o0 = a2
# o1 = a3
# p0 = a2|a3|a4
# p1 = a3|a4|a5
if kind == :method && (lead_tys.size < lead_num + post_num || lead_num + opt.size - 1 + post_num < lead_tys.size)
return argument_error(lead_tys.size, lead_num + post_num, lead_num + post_num + opt.size - 1)
end
# l0, l1
lead_num.times {|i| locals[i] = lead_tys[i] || Type.nil }
# o0, o1
opt_count = (lead_tys.size - lead_num - post_num).clamp(0, opt.size - 1)
(opt.size - 1).times {|i| locals[lead_num + i] = i < opt_count ? lead_tys[lead_num + i] : Type.nil }
# p0, p1
off = lead_num + opt_count
post_num.times {|i| locals[post_index + i] = lead_tys[off + i] || Type.nil }
end
end
kw_tys = @kw_tys.dup
if keyword
keyword.each_with_index do |kw, i|
case
when kw.is_a?(Symbol) # required keyword
key = kw
req = true
when kw.size == 2 # optional keyword (default value is a literal)
key, default_ty = *kw
default_ty = Type.guess_literal_type(default_ty)
default_ty = default_ty.base_type if default_ty.is_a?(Type::Literal)
req = false
else # optional keyword (default value is an expression)
key, = kw
req = false
end
if kw_tys.key?(key)
ty = kw_tys.delete(key)
else
ty = kw_tys[nil] || Type.bot
end
if ty == Type.bot && req
return "no argument for required keywords"
end
ty = ty.union(default_ty) if default_ty
locals[kw_index + i] = ty
end
end
if kwrest_index
if kw_tys.key?(nil)
kw_rest_ty = Type.gen_hash {|h| h[Type.any] = kw_tys[nil] }
else
kw_rest_ty = Type.gen_hash do |h|
kw_tys.each do |key, ty|
sym = Type::Symbol.new(key, Type::Instance.new(Type::Builtin[:sym]))
h[sym] = ty
end
end
end
locals[kwrest_index] = kw_rest_ty
else
if !kw_tys.empty?
return "unknown keyword: #{ kw_tys.keys.join(", ") }"
end
end
if block_index
locals[block_index] = @blk_ty
end
start_pcs = opt[0..opt_count]
return @blk_ty, start_pcs
end
end
end