typeprof 0.2.0 → 0.5.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.
Files changed (120) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/main.yml +1 -2
  3. data/.gitignore +1 -0
  4. data/Gemfile +2 -2
  5. data/Gemfile.lock +10 -21
  6. data/LICENSE +21 -0
  7. data/README.md +6 -0
  8. data/doc/demo.md +398 -0
  9. data/doc/doc.ja.md +6 -1
  10. data/doc/doc.md +7 -2
  11. data/exe/typeprof +2 -1
  12. data/lib/typeprof.rb +9 -0
  13. data/lib/typeprof/analyzer.rb +427 -325
  14. data/lib/typeprof/arguments.rb +405 -0
  15. data/lib/typeprof/block.rb +136 -0
  16. data/lib/typeprof/builtin.rb +36 -25
  17. data/lib/typeprof/cli.rb +51 -98
  18. data/lib/typeprof/config.rb +114 -0
  19. data/lib/typeprof/container-type.rb +280 -92
  20. data/lib/typeprof/export.rb +197 -108
  21. data/lib/typeprof/import.rb +134 -80
  22. data/lib/typeprof/iseq.rb +42 -1
  23. data/lib/typeprof/method.rb +178 -82
  24. data/lib/typeprof/type.rb +228 -369
  25. data/lib/typeprof/utils.rb +4 -18
  26. data/lib/typeprof/version.rb +3 -0
  27. data/smoke/arguments2.rb +55 -0
  28. data/smoke/array-each3.rb +1 -4
  29. data/smoke/array12.rb +1 -1
  30. data/smoke/array6.rb +1 -0
  31. data/smoke/block-ambiguous.rb +36 -0
  32. data/smoke/block-args1-rest.rb +62 -0
  33. data/smoke/block-args1.rb +59 -0
  34. data/smoke/block-args2-rest.rb +62 -0
  35. data/smoke/block-args2.rb +59 -0
  36. data/smoke/block-args3-rest.rb +73 -0
  37. data/smoke/block-args3.rb +70 -0
  38. data/smoke/block-blockarg.rb +27 -0
  39. data/smoke/block-kwarg.rb +52 -0
  40. data/smoke/block10.rb +1 -1
  41. data/smoke/block11.rb +1 -1
  42. data/smoke/block14.rb +17 -0
  43. data/smoke/block4.rb +2 -2
  44. data/smoke/block5.rb +1 -0
  45. data/smoke/block6.rb +1 -1
  46. data/smoke/block7.rb +0 -2
  47. data/smoke/block8.rb +2 -2
  48. data/smoke/block9.rb +1 -1
  49. data/smoke/blown.rb +1 -1
  50. data/smoke/class-hierarchy.rb +54 -0
  51. data/smoke/class-hierarchy2.rb +27 -0
  52. data/smoke/constant1.rb +11 -6
  53. data/smoke/constant2.rb +2 -0
  54. data/smoke/cvar.rb +1 -0
  55. data/smoke/demo10.rb +1 -1
  56. data/smoke/demo8.rb +2 -2
  57. data/smoke/demo9.rb +1 -3
  58. data/smoke/flow7.rb +1 -7
  59. data/smoke/flow8.rb +13 -0
  60. data/smoke/hash-fetch.rb +3 -3
  61. data/smoke/hash-merge-bang.rb +11 -0
  62. data/smoke/hash1.rb +1 -1
  63. data/smoke/hash3.rb +1 -1
  64. data/smoke/hash4.rb +1 -1
  65. data/smoke/instance_eval.rb +1 -1
  66. data/smoke/int_times.rb +1 -1
  67. data/smoke/ivar2.rb +1 -1
  68. data/smoke/keyword3.rb +1 -2
  69. data/smoke/keyword4.rb +1 -1
  70. data/smoke/kwsplat1.rb +1 -1
  71. data/smoke/kwsplat2.rb +1 -1
  72. data/smoke/module4.rb +2 -0
  73. data/smoke/multiple-superclass.rb +4 -0
  74. data/smoke/next2.rb +1 -1
  75. data/smoke/optional1.rb +1 -1
  76. data/smoke/optional2.rb +1 -1
  77. data/smoke/optional3.rb +10 -0
  78. data/smoke/pattern-match1.rb +23 -0
  79. data/smoke/pattern-match2.rb +15 -0
  80. data/smoke/proc4.rb +1 -1
  81. data/smoke/rbs-extend.rb +9 -0
  82. data/smoke/rbs-extend.rbs +7 -0
  83. data/smoke/rbs-interface.rb +24 -0
  84. data/smoke/rbs-interface.rbs +12 -0
  85. data/smoke/rbs-proc1.rb +9 -0
  86. data/smoke/rbs-proc1.rbs +3 -0
  87. data/smoke/rbs-proc2.rb +20 -0
  88. data/smoke/rbs-proc2.rbs +3 -0
  89. data/smoke/rbs-proc3.rb +13 -0
  90. data/smoke/rbs-proc3.rbs +4 -0
  91. data/smoke/rbs-record.rb +17 -0
  92. data/smoke/rbs-record.rbs +4 -0
  93. data/smoke/rbs-tyvar.rb +18 -0
  94. data/smoke/rbs-tyvar.rbs +5 -0
  95. data/smoke/rbs-tyvar2.rb +20 -0
  96. data/smoke/rbs-tyvar2.rbs +9 -0
  97. data/smoke/rbs-tyvar3.rb +17 -0
  98. data/smoke/rbs-tyvar3.rbs +5 -0
  99. data/smoke/rbs-tyvar4.rb +36 -0
  100. data/smoke/rbs-tyvar5.rb +12 -0
  101. data/smoke/rbs-tyvar5.rbs +8 -0
  102. data/smoke/rest1.rb +1 -1
  103. data/smoke/rest2.rb +1 -1
  104. data/smoke/rest3.rb +1 -1
  105. data/smoke/rest5.rb +1 -1
  106. data/smoke/rest6.rb +1 -1
  107. data/smoke/retry1.rb +1 -1
  108. data/smoke/return.rb +1 -1
  109. data/smoke/singleton_method.rb +3 -0
  110. data/smoke/step.rb +1 -1
  111. data/smoke/struct.rb +4 -3
  112. data/smoke/struct3.rb +14 -0
  113. data/smoke/symbol-proc.rb +24 -0
  114. data/smoke/uninitialize-var.rb +12 -0
  115. data/smoke/user-demo.rb +15 -0
  116. data/smoke/wrong-extend.rb +1 -0
  117. data/typeprof.gemspec +4 -2
  118. metadata +53 -6
  119. data/run.sh +0 -3
  120. data/smoke/variadic1.rb.notyet +0 -5
@@ -32,6 +32,23 @@ module TypeProf
32
32
  @name, @path, @absolute_path, @start_lineno, @type,
33
33
  @locals, @fargs_format, catch_table, insns = *iseq
34
34
 
35
+ case @type
36
+ when :method, :block
37
+ if @fargs_format[:opt]
38
+ label = @fargs_format[:opt].last
39
+ i = insns.index(label) + 1
40
+ else
41
+ i = insns.find_index {|insn| insn.is_a?(Array) }
42
+ end
43
+ # skip keyword initialization
44
+ while insns[i][0] == :checkkeyword
45
+ raise if insns[i + 1][0] != :branchif
46
+ label = insns[i + 1][1]
47
+ i = insns.index(label) + 1
48
+ end
49
+ insns[i, 0] = [[:_iseq_body_start]]
50
+ end
51
+
35
52
  @insns = []
36
53
  @linenos = []
37
54
 
@@ -79,7 +96,7 @@ module TypeProf
79
96
  nil
80
97
  when Array
81
98
  insn, *operands = e
82
- operands = INSN_TABLE[insn].zip(operands).map do |type, operand|
99
+ operands = (INSN_TABLE[insn] || []).zip(operands).map do |type, operand|
83
100
  case type
84
101
  when "ISEQ"
85
102
  operand && ISeq.new(operand)
@@ -200,6 +217,30 @@ module TypeProf
200
217
  @insns[j + 1] = [:send_branch, [getlocal_operands, send_operands, branch_operands]]
201
218
  end
202
219
 
220
+ # find a pattern: getlocal, dup, branch
221
+ (@insns.size - 2).times do |i|
222
+ next if branch_targets[i + 1] || branch_targets[i + 2]
223
+ insn0, getlocal_operands = @insns[i]
224
+ insn1, dup_operands = @insns[i + 1]
225
+ insn2, branch_operands = @insns[i + 2]
226
+ if insn0 == :getlocal && insn1 == :dup && insn2 == :branch && getlocal_operands[1] == 0
227
+ @insns[i ] = [:nop]
228
+ @insns[i + 1] = [:nop]
229
+ @insns[i + 2] = [:getlocal_dup_branch, [getlocal_operands, dup_operands, branch_operands]]
230
+ end
231
+ end
232
+
233
+ # find a pattern: dup, branch
234
+ (@insns.size - 1).times do |i|
235
+ next if branch_targets[i + 1]
236
+ insn0, dup_operands = @insns[i]
237
+ insn1, branch_operands = @insns[i + 1]
238
+ if insn0 == :dup && insn1 == :branch
239
+ @insns[i ] = [:nop]
240
+ @insns[i + 1] = [:dup_branch, [dup_operands, branch_operands]]
241
+ end
242
+ end
243
+
203
244
  # find a pattern: getlocal, branch
204
245
  (@insns.size - 1).times do |i|
205
246
  next if branch_targets[i + 1]
@@ -11,101 +11,121 @@ module TypeProf
11
11
  end
12
12
 
13
13
  def do_send(recv, mid, aargs, caller_ep, caller_env, scratch, &ctn)
14
+ recv = recv.base_type while recv.respond_to?(:base_type)
14
15
  recv = scratch.globalize_type(recv, caller_env, caller_ep)
15
16
  aargs = scratch.globalize_type(aargs, caller_env, caller_ep)
16
17
 
17
- aargs.each_formal_arguments(@iseq.fargs_format) do |fargs, start_pc|
18
- if fargs.is_a?(String)
19
- scratch.error(caller_ep, fargs)
20
- ctn[Type.any, caller_ep, caller_env]
21
- next
22
- end
18
+ locals = [Type.nil] * @iseq.locals.size
19
+
20
+ blk_ty, start_pcs = aargs.setup_formal_arguments(:method, locals, @iseq.fargs_format)
21
+ if blk_ty.is_a?(String)
22
+ scratch.error(caller_ep, blk_ty)
23
+ ctn[Type.any, caller_ep, caller_env]
24
+ return
25
+ end
23
26
 
24
- callee_ep = do_send_core(fargs, start_pc, recv, mid, scratch)
27
+ nctx = Context.new(@iseq, @cref, mid)
28
+ callee_ep = ExecutionPoint.new(nctx, 0, nil)
29
+ nenv = Env.new(StaticEnv.new(recv, blk_ty, false), locals, [], Utils::HashWrapper.new({}))
30
+ alloc_site = AllocationSite.new(callee_ep)
31
+ locals.each_with_index do |ty, i|
32
+ alloc_site2 = alloc_site.add_id(i)
33
+ # nenv is top-level, so it is okay to call Type#localize directly
34
+ nenv, ty = ty.localize(nenv, alloc_site2, Config.options[:type_depth_limit])
35
+ nenv = nenv.local_update(i, ty)
36
+ end
25
37
 
26
- scratch.add_iseq_method_call!(self, callee_ep.ctx)
27
- scratch.add_callsite!(callee_ep.ctx, fargs, caller_ep, caller_env, &ctn)
38
+ start_pcs.each do |start_pc|
39
+ scratch.merge_env(ExecutionPoint.new(nctx, start_pc, @outer_ep), nenv)
28
40
  end
41
+
42
+ scratch.add_iseq_method_call!(self, nctx)
43
+ scratch.add_callsite!(nctx, caller_ep, caller_env, &ctn)
29
44
  end
30
45
 
31
- def do_send_core(fargs, start_pc, recv, mid, scratch)
46
+ def do_check_send(msig, recv, mid, ep, scratch)
47
+ lead_num = @iseq.fargs_format[:lead_num] || 0
48
+ post_num = @iseq.fargs_format[:post_num] || 0
49
+ rest_start = @iseq.fargs_format[:rest_start]
50
+ opt = @iseq.fargs_format[:opt] || [0]
51
+
52
+ # TODO: check keywords
53
+ if rest_start
54
+ # almost ok
55
+ else
56
+ if msig.lead_tys.size + msig.post_tys.size < lead_num + post_num
57
+ scratch.error(ep, "RBS says that the arity may be %d, but the method definition requires at least %d arguments" % [msig.lead_tys.size + msig.post_tys.size, lead_num + post_num])
58
+ return
59
+ end
60
+ if msig.lead_tys.size + msig.opt_tys.size + msig.post_tys.size > lead_num + opt.size - 1 + post_num
61
+ scratch.error(ep, "RBS says that the arity may be %d, but the method definition requires at most %d arguments" % [msig.lead_tys.size + msig.opt_tys.size + msig.post_tys.size, lead_num + opt.size - 1 + post_num])
62
+ return
63
+ end
64
+ end
65
+
32
66
  lead_num = @iseq.fargs_format[:lead_num] || 0
33
67
  post_start = @iseq.fargs_format[:post_start]
34
68
  rest_start = @iseq.fargs_format[:rest_start]
35
69
  kw_start = @iseq.fargs_format[:kwbits]
36
70
  kw_start -= @iseq.fargs_format[:keyword].size if kw_start
71
+ kw_rest = @iseq.fargs_format[:kwrest]
37
72
  block_start = @iseq.fargs_format[:block_start]
38
73
 
39
- # XXX: need to check .rbs fargs and .rb fargs
74
+ # XXX: need to check .rbs msig and .rb fargs
40
75
 
41
76
  ctx = Context.new(@iseq, @cref, mid)
42
- callee_ep = ExecutionPoint.new(ctx, start_pc, nil)
77
+ callee_ep = ExecutionPoint.new(ctx, 0, nil)
43
78
 
44
79
  locals = [Type.nil] * @iseq.locals.size
45
- nenv = Env.new(StaticEnv.new(recv, fargs.blk_ty, false), locals, [], Utils::HashWrapper.new({}))
80
+ nenv = Env.new(StaticEnv.new(recv, msig.blk_ty, false), locals, [], Utils::HashWrapper.new({}))
46
81
  alloc_site = AllocationSite.new(callee_ep)
47
82
  idx = 0
48
- fargs.lead_tys.each_with_index do |ty, i|
83
+ msig.lead_tys.each_with_index do |ty, i|
49
84
  alloc_site2 = alloc_site.add_id(idx += 1)
50
85
  # nenv is top-level, so it is okay to call Type#localize directly
51
86
  nenv, ty = ty.localize(nenv, alloc_site2, Config.options[:type_depth_limit])
52
87
  nenv = nenv.local_update(i, ty)
53
88
  end
54
- if fargs.opt_tys
55
- fargs.opt_tys.each_with_index do |ty, i|
89
+ if msig.opt_tys
90
+ msig.opt_tys.each_with_index do |ty, i|
56
91
  alloc_site2 = alloc_site.add_id(idx += 1)
57
92
  nenv, ty = ty.localize(nenv, alloc_site2, Config.options[:type_depth_limit])
58
93
  nenv = nenv.local_update(lead_num + i, ty)
59
94
  end
60
95
  end
61
- if fargs.rest_ty
96
+ if msig.rest_ty
62
97
  alloc_site2 = alloc_site.add_id(idx += 1)
63
- ty = Type::Array.new(Type::Array::Elements.new([], fargs.rest_ty), Type::Instance.new(Type::Builtin[:ary]))
98
+ ty = Type::Array.new(Type::Array::Elements.new([], msig.rest_ty), Type::Instance.new(Type::Builtin[:ary]))
64
99
  nenv, rest_ty = ty.localize(nenv, alloc_site2, Config.options[:type_depth_limit])
65
100
  nenv = nenv.local_update(rest_start, rest_ty)
66
101
  end
67
- if fargs.post_tys
68
- fargs.post_tys.each_with_index do |ty, i|
102
+ if msig.post_tys
103
+ msig.post_tys.each_with_index do |ty, i|
69
104
  alloc_site2 = alloc_site.add_id(idx += 1)
70
105
  nenv, ty = ty.localize(nenv, alloc_site2, Config.options[:type_depth_limit])
71
106
  nenv = nenv.local_update(post_start + i, ty)
72
107
  end
73
108
  end
74
- if fargs.kw_tys
75
- fargs.kw_tys.each_with_index do |(_, _, ty), i|
76
- alloc_site2 = alloc_site.add_id(idx += 1)
109
+ if msig.kw_tys
110
+ msig.kw_tys.each_with_index do |(_, key, ty), i|
111
+ alloc_site2 = alloc_site.add_id(key)
77
112
  nenv, ty = ty.localize(nenv, alloc_site2, Config.options[:type_depth_limit])
78
113
  nenv = nenv.local_update(kw_start + i, ty)
79
114
  end
80
115
  end
81
- # kwrest
82
- nenv = nenv.local_update(block_start, fargs.blk_ty) if block_start
83
-
84
- scratch.merge_env(callee_ep, nenv)
85
-
86
- callee_ep
87
- end
88
-
89
- def do_check_send_core(fargs, recv, mid, ep, scratch)
90
- lead_num = @iseq.fargs_format[:lead_num] || 0
91
- post_num = @iseq.fargs_format[:post_num] || 0
92
- rest_start = @iseq.fargs_format[:rest_start]
93
- opt = @iseq.fargs_format[:opt] || [0]
116
+ if msig.kw_rest_ty
117
+ ty = msig.kw_rest_ty
118
+ alloc_site2 = alloc_site.add_id(:**)
119
+ nenv, ty = ty.localize(nenv, alloc_site2, Config.options[:type_depth_limit])
120
+ nenv = nenv.local_update(kw_rest, ty)
121
+ end
122
+ nenv = nenv.local_update(block_start, msig.blk_ty) if block_start
94
123
 
95
- # TODO: check keywords
96
- if rest_start
97
- # almost ok
98
- else
99
- if fargs.lead_tys.size + fargs.post_tys.size < lead_num + post_num
100
- scratch.error(ep, "RBS says that the arity may be %d, but the method definition requires at least %d arguments" % [fargs.lead_tys.size + fargs.post_tys.size, lead_num + post_num])
101
- return
102
- end
103
- if fargs.lead_tys.size + fargs.opt_tys.size + fargs.post_tys.size > lead_num + opt.size - 1 + post_num
104
- scratch.error(ep, "RBS says that the arity may be %d, but the method definition requires at most %d arguments" % [fargs.lead_tys.size + fargs.opt_tys.size + fargs.post_tys.size, lead_num + opt.size - 1 + post_num])
105
- return
106
- end
124
+ opt.each do |start_pc|
125
+ scratch.merge_env(ExecutionPoint.new(ctx, start_pc, nil), nenv)
107
126
  end
108
- do_send_core(fargs, 0, recv, mid, scratch)
127
+
128
+ ctx
109
129
  end
110
130
  end
111
131
 
@@ -141,8 +161,8 @@ module TypeProf
141
161
  end
142
162
 
143
163
  class TypedMethodDef < MethodDef
144
- def initialize(sigs, rbs_source) # sigs: Array<[FormalArguments, (return)Type]>
145
- @sigs = sigs
164
+ def initialize(sig_rets, rbs_source) # sig_rets: Array<[MethodSignature, (return)Type]>
165
+ @sig_rets = sig_rets
146
166
  @rbs_source = rbs_source
147
167
  end
148
168
 
@@ -152,45 +172,78 @@ module TypeProf
152
172
  recv = scratch.globalize_type(recv_orig, caller_env, caller_ep)
153
173
  found = false
154
174
  aargs = scratch.globalize_type(aargs, caller_env, caller_ep)
155
- @sigs.each do |fargs, ret_ty|
175
+ @sig_rets.each do |msig, ret_ty|
156
176
  ncaller_env = caller_env
157
- #pp [mid, aargs, fargs]
158
- # XXX: support self type in fargs
159
- subst = { Type::Var.new(:self) => recv }
160
- next unless aargs.consistent_with_formal_arguments?(fargs, subst)
161
- if recv.is_a?(Type::Array) && recv_orig.is_a?(Type::LocalArray)
177
+ #pp [mid, aargs, msig]
178
+ # XXX: support self type in msig
179
+ subst = aargs.consistent_with_method_signature?(msig)
180
+ next unless subst
181
+ # need to check self tyvar?
182
+ subst[Type::Var.new(:self)] = recv
183
+ case
184
+ when recv.is_a?(Type::Cell) && recv_orig.is_a?(Type::LocalCell)
185
+ tyvars = recv.base_type.klass.type_params.map {|name,| Type::Var.new(name) }
186
+ tyvars.each_with_index do |tyvar, idx|
187
+ ty = subst[tyvar]
188
+ if ty
189
+ ncaller_env, ty = scratch.localize_type(ty, ncaller_env, caller_ep)
190
+ ncaller_env = scratch.update_container_elem_types(ncaller_env, caller_ep, recv_orig.id, recv_orig.base_type) do |elems|
191
+ elems.update(idx, ty)
192
+ end
193
+ end
194
+ end
195
+ tyvars.zip(recv.elems.elems) do |tyvar, elem|
196
+ if subst[tyvar]
197
+ subst[tyvar] = subst[tyvar].union(elem)
198
+ else
199
+ subst[tyvar] = elem
200
+ end
201
+ end
202
+ when recv.is_a?(Type::Array) && recv_orig.is_a?(Type::LocalArray)
162
203
  tyvar_elem = Type::Var.new(:Elem)
163
204
  if subst[tyvar_elem]
164
205
  ty = subst[tyvar_elem]
165
- alloc_site = AllocationSite.new(caller_ep).add_id(self)
166
206
  ncaller_env, ty = scratch.localize_type(ty, ncaller_env, caller_ep)
167
- ncaller_env = scratch.update_container_elem_types(ncaller_env, caller_ep, recv_orig.id) do |elems|
207
+ ncaller_env = scratch.update_container_elem_types(ncaller_env, caller_ep, recv_orig.id, recv_orig.base_type) do |elems|
168
208
  elems.update(nil, ty)
169
209
  end
170
210
  end
171
211
  subst.merge!({ tyvar_elem => recv.elems.squash })
172
- elsif recv.is_a?(Type::Hash) && recv_orig.is_a?(Type::LocalHash)
212
+ when recv.is_a?(Type::Hash) && recv_orig.is_a?(Type::LocalHash)
173
213
  tyvar_k = Type::Var.new(:K)
174
214
  tyvar_v = Type::Var.new(:V)
175
- # XXX: need to support destructive operation
176
- k_ty, v_ty = recv.elems.squash
215
+ k_ty0, v_ty0 = recv.elems.squash
216
+ if subst[tyvar_k] && subst[tyvar_v]
217
+ k_ty = subst[tyvar_k]
218
+ v_ty = subst[tyvar_v]
219
+ k_ty0 = k_ty0.union(k_ty)
220
+ v_ty0 = v_ty0.union(v_ty)
221
+ alloc_site = AllocationSite.new(caller_ep)
222
+ ncaller_env, k_ty = scratch.localize_type(k_ty, ncaller_env, caller_ep, alloc_site.add_id(:k))
223
+ ncaller_env, v_ty = scratch.localize_type(v_ty, ncaller_env, caller_ep, alloc_site.add_id(:v))
224
+ ncaller_env = scratch.update_container_elem_types(ncaller_env, caller_ep, recv_orig.id, recv_orig.base_type) do |elems|
225
+ elems.update(k_ty, v_ty)
226
+ end
227
+ end
177
228
  # XXX: need to heuristically replace ret type Hash[K, V] with self, instead of conversative type?
178
- subst.merge!({ tyvar_k => k_ty, tyvar_v => v_ty })
229
+ subst.merge!({ tyvar_k => k_ty0, tyvar_v => v_ty0 })
179
230
  end
180
- ret_ty = ret_ty.substitute(subst, Config.options[:type_depth_limit])
181
231
  found = true
182
- if aargs.blk_ty.is_a?(Type::ISeqProc)
232
+ if aargs.blk_ty.is_a?(Type::Proc)
233
+ #raise NotImplementedError unless aargs.blk_ty.block_body.is_a?(ISeqBlock) # XXX
183
234
  dummy_ctx = TypedContext.new(caller_ep, mid)
184
235
  dummy_ep = ExecutionPoint.new(dummy_ctx, -1, caller_ep)
185
- dummy_env = Env.new(StaticEnv.new(recv, fargs.blk_ty, false), [], [], Utils::HashWrapper.new({}))
186
- if fargs.blk_ty.is_a?(Type::TypedProc)
187
- scratch.add_callsite!(dummy_ctx, nil, caller_ep, ncaller_env, &ctn)
188
- nfargs = fargs.blk_ty.fargs
236
+ s_recv = recv
237
+ s_recv = s_recv.base_type while s_recv.respond_to?(:base_type)
238
+ dummy_env = Env.new(StaticEnv.new(s_recv, msig.blk_ty, false), [], [], Utils::HashWrapper.new({}))
239
+ if msig.blk_ty.is_a?(Type::Proc)
240
+ scratch.add_callsite!(dummy_ctx, caller_ep, ncaller_env, &ctn)
241
+ nfargs = msig.blk_ty.block_body.msig
189
242
  alloc_site = AllocationSite.new(caller_ep).add_id(self)
190
243
  nlead_tys = (nfargs.lead_tys + nfargs.opt_tys).map.with_index do |ty, i|
191
244
  if recv.is_a?(Type::Array)
192
245
  tyvar_elem = Type::Var.new(:Elem)
193
- ty = ty.substitute(subst.merge({ tyvar_elem => recv.elems.squash }), Config.options[:type_depth_limit])
246
+ ty = ty.substitute(subst.merge({ tyvar_elem => recv.elems.squash_or_any }), Config.options[:type_depth_limit])
194
247
  else
195
248
  ty = ty.substitute(subst, Config.options[:type_depth_limit])
196
249
  end
@@ -200,15 +253,57 @@ module TypeProf
200
253
  ty
201
254
  end
202
255
  0.upto(nfargs.opt_tys.size) do |n|
203
- naargs = ActualArguments.new(nlead_tys[0, nfargs.lead_tys.size + n], nil, nil, Type.nil) # XXX: support block to block?
204
- scratch.do_invoke_block(false, aargs.blk_ty, naargs, dummy_ep, dummy_env) do |blk_ret_ty, _ep, _env|
205
- subst2 = {}
206
- if blk_ret_ty.consistent?(fargs.blk_ty.ret_ty, subst2)
207
- if recv.is_a?(Type::Array) && recv_orig.is_a?(Type::LocalArray)
256
+ naargs = ActualArguments.new(nlead_tys[0, nfargs.lead_tys.size + n], nil, {}, Type.nil) # XXX: support block to block?
257
+ scratch.do_invoke_block(aargs.blk_ty, naargs, dummy_ep, dummy_env) do |blk_ret_ty, _ep, _env|
258
+ subst2 = Type.match?(blk_ret_ty, msig.blk_ty.block_body.ret_ty)
259
+ if subst2
260
+ subst2 = Type.merge_substitution(subst, subst2)
261
+ case
262
+ when recv.is_a?(Type::Cell) && recv_orig.is_a?(Type::LocalCell)
263
+ tyvars = recv.base_type.klass.type_params.map {|name,| Type::Var.new(name) }
264
+ tyvars.each_with_index do |tyvar, idx|
265
+ ty = subst2[tyvar]
266
+ if ty
267
+ ncaller_env, ty = scratch.localize_type(ty, ncaller_env, caller_ep)
268
+ ncaller_env = scratch.update_container_elem_types(ncaller_env, caller_ep, recv_orig.id, recv_orig.base_type) do |elems|
269
+ elems.update(idx, ty)
270
+ end
271
+ scratch.merge_return_env(caller_ep) {|env| env ? env.merge(ncaller_env) : ncaller_env }
272
+ end
273
+ end
274
+ tyvars.zip(recv.elems.elems) do |tyvar, elem|
275
+ if subst2[tyvar]
276
+ subst2[tyvar] = subst2[tyvar].union(elem)
277
+ else
278
+ subst2[tyvar] = elem
279
+ end
280
+ end
281
+ ret_ty = ret_ty.substitute(subst2, Config.options[:type_depth_limit])
282
+ when recv.is_a?(Type::Array) && recv_orig.is_a?(Type::LocalArray)
208
283
  tyvar_elem = Type::Var.new(:Elem)
209
284
  if subst2[tyvar_elem]
210
- ncaller_env = scratch.update_container_elem_types(ncaller_env, caller_ep, recv_orig.id) do |elems|
211
- elems.update(nil, subst2[tyvar_elem])
285
+ ty = subst2[tyvar_elem]
286
+ ncaller_env, ty = scratch.localize_type(ty, ncaller_env, caller_ep)
287
+ ncaller_env = scratch.update_container_elem_types(ncaller_env, caller_ep, recv_orig.id, recv_orig.base_type) do |elems|
288
+ elems.update(nil, ty)
289
+ end
290
+ scratch.merge_return_env(caller_ep) {|env| env ? env.merge(ncaller_env) : ncaller_env }
291
+ end
292
+ ret_ty = ret_ty.substitute(subst2, Config.options[:type_depth_limit])
293
+ when recv.is_a?(Type::Hash) && recv_orig.is_a?(Type::LocalHash)
294
+ tyvar_k = Type::Var.new(:K)
295
+ tyvar_v = Type::Var.new(:V)
296
+ k_ty0, v_ty0 = recv.elems.squash
297
+ if subst2[tyvar_k] && subst2[tyvar_v]
298
+ k_ty = subst2[tyvar_k]
299
+ v_ty = subst2[tyvar_v]
300
+ k_ty0 = k_ty0.union(k_ty)
301
+ v_ty0 = v_ty0.union(v_ty)
302
+ alloc_site = AllocationSite.new(caller_ep)
303
+ ncaller_env, k_ty = scratch.localize_type(k_ty, ncaller_env, caller_ep, alloc_site.add_id(:k))
304
+ ncaller_env, v_ty = scratch.localize_type(v_ty, ncaller_env, caller_ep, alloc_site.add_id(:v))
305
+ ncaller_env = scratch.update_container_elem_types(ncaller_env, caller_ep, recv_orig.id, recv_orig.base_type) do |elems|
306
+ elems.update(k_ty, v_ty)
212
307
  end
213
308
  scratch.merge_return_env(caller_ep) {|env| env ? env.merge(ncaller_env) : ncaller_env }
214
309
  end
@@ -223,10 +318,10 @@ module TypeProf
223
318
  end
224
319
  ret_ty = ret_ty.remove_type_vars
225
320
  # XXX: check the return type from the block
226
- # sig.blk_ty.ret_ty.eql?(_ret_ty) ???
227
- scratch.add_return_type!(dummy_ctx, ret_ty)
321
+ # sig.blk_ty.block_body.ret_ty.eql?(_ret_ty) ???
322
+ scratch.add_return_value!(dummy_ctx, ret_ty)
228
323
  end
229
- # scratch.add_return_type!(dummy_ctx, ret_ty) ?
324
+ # scratch.add_return_value!(dummy_ctx, ret_ty) ?
230
325
  # This makes `def foo; 1.times { return "str" }; end` return Integer|String
231
326
  end
232
327
  else
@@ -236,6 +331,7 @@ module TypeProf
236
331
  ctn[ret_ty, caller_ep, ncaller_env]
237
332
  end
238
333
  else
334
+ ret_ty = ret_ty.substitute(subst, Config.options[:type_depth_limit])
239
335
  ret_ty = ret_ty.remove_type_vars
240
336
  ctn[ret_ty, caller_ep, ncaller_env]
241
337
  end
@@ -249,8 +345,8 @@ module TypeProf
249
345
 
250
346
  def do_match_iseq_mdef(iseq_mdef, recv, mid, env, ep, scratch)
251
347
  recv = scratch.globalize_type(recv, env, ep)
252
- @sigs.each do |fargs, _ret_ty|
253
- iseq_mdef.do_check_send_core(fargs, recv, mid, ep, scratch)
348
+ @sig_rets.each do |msig, _ret_ty|
349
+ iseq_mdef.do_check_send(msig, recv, mid, ep, scratch)
254
350
  end
255
351
  end
256
352
  end
@@ -3,7 +3,7 @@ module TypeProf
3
3
  include Utils::StructuralEquality
4
4
 
5
5
  def initialize
6
- raise "cannot instanciate abstract type"
6
+ raise "cannot instantiate abstract type"
7
7
  end
8
8
 
9
9
  Builtin = {}
@@ -20,16 +20,69 @@ module TypeProf
20
20
  self
21
21
  end
22
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)
23
+ def self.match?(ty1, ty2)
24
+ # both ty1 and ty2 should be global
25
+ # ty1 is always concrete; it should not have type variables
26
+ # ty2 might be abstract; it may have type variables
27
+ case ty2
28
+ when Type::Var
29
+ { ty2 => ty1 }
30
+ when Type::Any
31
+ {}
27
32
  when Type::Union
28
- other.types.each do |ty2|
29
- return true if consistent?(ty2, subst)
33
+ subst = nil
34
+ ty2.each_child_global do |ty2|
35
+ # this is very conservative to create subst:
36
+ # Type.match?( int | str, int | X) creates { X => int | str } but should be { X => str }???
37
+ subst2 = Type.match?(ty1, ty2)
38
+ next unless subst2
39
+ subst = Type.merge_substitution(subst, subst2)
30
40
  end
41
+ subst
31
42
  else
32
- self == other
43
+ case ty1
44
+ when Type::Var then raise "should not occur"
45
+ when Type::Any
46
+ subst = {}
47
+ ty2.each_free_type_variable do |tyvar|
48
+ subst[tyvar] = Type.any
49
+ end
50
+ subst
51
+ when Type::Union
52
+ subst = nil
53
+ ty1.each_child_global do |ty1|
54
+ subst2 = Type.match?(ty1, ty2)
55
+ next unless subst2
56
+ subst = Type.merge_substitution(subst, subst2)
57
+ end
58
+ subst
59
+ else
60
+ if ty2.is_a?(Type::ContainerType)
61
+ # ty2 may have type variables
62
+ return nil if ty1.class != ty2.class
63
+ ty1.match?(ty2)
64
+ elsif ty1.is_a?(Type::ContainerType)
65
+ nil
66
+ else
67
+ ty1.consistent?(ty2) ? {} : nil
68
+ end
69
+ end
70
+ end
71
+ end
72
+
73
+ def self.merge_substitution(subst1, subst2)
74
+ if subst1
75
+ subst1 = subst1.dup
76
+ subst2.each do |tyvar, ty|
77
+ if subst1[tyvar]
78
+ subst1[tyvar] = subst1[tyvar].union(ty)
79
+ else
80
+ subst1[tyvar] = ty
81
+ end
82
+ end
83
+ subst1
84
+ else
85
+ subst2
33
86
  end
34
87
  end
35
88
 
@@ -41,6 +94,9 @@ module TypeProf
41
94
  yield self
42
95
  end
43
96
 
97
+ def each_free_type_variable
98
+ end
99
+
44
100
  def union(other)
45
101
  return self if self == other # fastpath
46
102
 
@@ -118,10 +174,8 @@ module TypeProf
118
174
  nil
119
175
  end
120
176
 
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
177
+ def consistent?(_other)
178
+ raise "should not be called"
125
179
  end
126
180
 
127
181
  def substitute(_subst, _depth)
@@ -129,6 +183,17 @@ module TypeProf
129
183
  end
130
184
  end
131
185
 
186
+ class Void < Any
187
+ def inspect
188
+ "Type::Void"
189
+ end
190
+
191
+ def screen_name(scratch)
192
+ "void"
193
+ end
194
+ end
195
+
196
+
132
197
  class Union < Type
133
198
  def initialize(tys, elems)
134
199
  raise unless tys.is_a?(Utils::Set)
@@ -137,7 +202,7 @@ module TypeProf
137
202
  # invariant check
138
203
  local = nil
139
204
  tys.each do |ty|
140
- raise unless ty.is_a?(Type)
205
+ raise ty.inspect unless ty.is_a?(Type)
141
206
  local = true if ty.is_a?(LocalArray) || ty.is_a?(LocalHash)
142
207
  end
143
208
  raise if local && elems
@@ -145,6 +210,12 @@ module TypeProf
145
210
  @elems = elems
146
211
  end
147
212
 
213
+ def each_free_type_variable(&blk)
214
+ each_child_global do |ty|
215
+ ty.each_free_type_variable(&blk)
216
+ end
217
+ end
218
+
148
219
  def limit_size(limit)
149
220
  return Type.any if limit <= 0
150
221
  tys = Utils::Set[]
@@ -215,12 +286,15 @@ module TypeProf
215
286
  bool = true
216
287
  end
217
288
  types.delete(Type.any) unless Config.options[:pedantic_output]
289
+ proc_tys, types = types.partition {|ty| ty.is_a?(Proc) }
218
290
  types = types.map {|ty| ty.screen_name(scratch) }
291
+ types << scratch.show_proc_signature(proc_tys) unless proc_tys.empty?
219
292
  types << "bool" if bool
220
293
  types = types.sort
221
294
  if optional
222
- if types.size == 1
223
- types.first + "?"
295
+ case types.size
296
+ when 0 then "nil"
297
+ when 1 then types.first + "?"
224
298
  else
225
299
  "(#{ types.join (" | ") })?"
226
300
  end
@@ -257,48 +331,20 @@ module TypeProf
257
331
  def localize(env, alloc_site, depth)
258
332
  return env, Type.any if depth <= 0
259
333
  tys = @types.map do |ty|
260
- alloc_site2 = alloc_site.add_id(ty)
261
- env, ty2 = ty.localize(env, alloc_site2, depth - 1)
334
+ env, ty2 = ty.localize(env, alloc_site, depth - 1)
262
335
  ty2
263
336
  end
264
337
  @elems&.each do |(container_kind, base_type), elems|
265
338
  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)
339
+ env, ty = ty.localize(env, alloc_site, depth - 1)
268
340
  tys = tys.add(ty)
269
341
  end
270
342
  ty = Union.new(tys, nil).normalize
271
343
  return env, ty
272
344
  end
273
345
 
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
346
+ def consistent?(_other)
347
+ raise "should not be called"
302
348
  end
303
349
 
304
350
  def substitute(subst, depth)
@@ -317,7 +363,7 @@ module TypeProf
317
363
  elems = @elems&.to_h do |(container_kind, base_type), elems|
318
364
  [[container_kind, base_type], elems.substitute(subst, depth - 1)]
319
365
  end
320
- ty = Union.new(tys, elems)
366
+ ty = Union.new(tys, elems).normalize
321
367
  unions.each do |ty0|
322
368
  ty = ty.union(ty0)
323
369
  end
@@ -357,6 +403,10 @@ module TypeProf
357
403
  "Var[#{ @name }]"
358
404
  end
359
405
 
406
+ def each_free_type_variable
407
+ yield self
408
+ end
409
+
360
410
  def substitute(subst, depth)
361
411
  if subst[self]
362
412
  subst[self].limit_size(depth)
@@ -365,7 +415,7 @@ module TypeProf
365
415
  end
366
416
  end
367
417
 
368
- def consistent?(other, subst)
418
+ def consistent?(_other)
369
419
  raise "should not be called: #{ self }"
370
420
  end
371
421
 
@@ -406,15 +456,8 @@ module TypeProf
406
456
  scratch.get_method(self, true, mid)
407
457
  end
408
458
 
409
- def consistent?(other, subst)
459
+ def consistent?(other)
410
460
  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
461
  when Type::Class
419
462
  ty = self
420
463
  loop do
@@ -465,17 +508,10 @@ module TypeProf
465
508
  scratch.get_method(@klass, false, mid)
466
509
  end
467
510
 
468
- def consistent?(other, subst)
511
+ def consistent?(other)
469
512
  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
513
  when Type::Instance
478
- @klass.consistent?(other.klass, subst)
514
+ @klass.consistent?(other.klass)
479
515
  when Type::Class
480
516
  return true if @klass == Type::Builtin[:obj] || @klass == Type::Builtin[:class] || @klass == Type::Builtin[:module]
481
517
  return false
@@ -489,6 +525,7 @@ module TypeProf
489
525
  end
490
526
  end
491
527
 
528
+ # This is an internal object in MRI, so a user program cannot create this object explicitly
492
529
  class ISeq < Type
493
530
  def initialize(iseq)
494
531
  @iseq = iseq
@@ -505,66 +542,53 @@ module TypeProf
505
542
  end
506
543
  end
507
544
 
508
- class ISeqProc < Type
509
- def initialize(iseq, ep, type)
510
- @iseq = iseq
511
- @ep = ep
512
- @type = type
545
+ class Proc < Type
546
+ def initialize(block_body, base_type)
547
+ @block_body, @base_type = block_body, base_type
513
548
  end
514
549
 
515
- attr_reader :iseq, :ep, :type
516
-
517
- def inspect
518
- "#<ISeqProc>"
519
- end
550
+ attr_reader :block_body, :base_type
520
551
 
521
- def screen_name(scratch)
522
- "Proc[#{ scratch.proc_screen_name(self) }]" # TODO: use RBS syntax
552
+ def consistent?(other)
553
+ case other
554
+ when Type::Proc
555
+ @block_body.consistent?(other.block_body)
556
+ else
557
+ self == other
558
+ end
523
559
  end
524
560
 
525
561
  def get_method(mid, scratch)
526
- @type.get_method(mid, scratch)
562
+ @base_type.get_method(mid, scratch)
527
563
  end
528
564
 
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
- @fargs = fargs
537
- @ret_ty = ret_ty
538
- @type = type
565
+ def substitute(subst, depth)
566
+ Proc.new(@block_body.substitute(subst, depth), @base_type)
539
567
  end
540
568
 
541
- attr_reader :fargs, :ret_ty
542
-
543
569
  def screen_name(scratch)
544
- "TypedProc[...]" # TODO: use RBS syntax
570
+ scratch.show_proc_signature([self])
545
571
  end
546
572
  end
547
573
 
548
574
  class Symbol < Type
549
- def initialize(sym, type)
575
+ def initialize(sym, base_type)
550
576
  @sym = sym
551
- @type = type
577
+ @base_type = base_type
552
578
  end
553
579
 
554
- attr_reader :sym, :type
580
+ attr_reader :sym, :base_type
555
581
 
556
582
  def inspect
557
- "Type::Symbol[#{ @sym ? @sym.inspect : "(dynamic symbol)" }, #{ @type.inspect }]"
583
+ "Type::Symbol[#{ @sym ? @sym.inspect : "(dynamic symbol)" }, #{ @base_type.inspect }]"
558
584
  end
559
585
 
560
- def consistent?(other, subst)
586
+ def consistent?(other)
561
587
  case other
562
- when Var
563
- other.add_subst!(self, subst)
564
588
  when Symbol
565
589
  @sym == other.sym
566
590
  else
567
- @type.consistent?(other, subst)
591
+ @base_type.consistent?(other)
568
592
  end
569
593
  end
570
594
 
@@ -572,12 +596,12 @@ module TypeProf
572
596
  if @sym
573
597
  @sym.inspect
574
598
  else
575
- @type.screen_name(scratch)
599
+ @base_type.screen_name(scratch)
576
600
  end
577
601
  end
578
602
 
579
603
  def get_method(mid, scratch)
580
- @type.get_method(mid, scratch)
604
+ @base_type.get_method(mid, scratch)
581
605
  end
582
606
 
583
607
  def substitute(_subst, _depth)
@@ -585,33 +609,33 @@ module TypeProf
585
609
  end
586
610
  end
587
611
 
588
- # local info
612
+ # A local type
589
613
  class Literal < Type
590
- def initialize(lit, type)
614
+ def initialize(lit, base_type)
591
615
  @lit = lit
592
- @type = type
616
+ @base_type = base_type
593
617
  end
594
618
 
595
- attr_reader :lit, :type
619
+ attr_reader :lit, :base_type
596
620
 
597
621
  def inspect
598
- "Type::Literal[#{ @lit.inspect }, #{ @type.inspect }]"
622
+ "Type::Literal[#{ @lit.inspect }, #{ @base_type.inspect }]"
599
623
  end
600
624
 
601
625
  def screen_name(scratch)
602
- @type.screen_name(scratch) + "<#{ @lit.inspect }>"
626
+ @base_type.screen_name(scratch) + "<#{ @lit.inspect }>"
603
627
  end
604
628
 
605
629
  def globalize(_env, _visited, _depth)
606
- @type
630
+ @base_type
607
631
  end
608
632
 
609
633
  def get_method(mid, scratch)
610
- @type.get_method(mid, scratch)
634
+ @base_type.get_method(mid, scratch)
611
635
  end
612
636
 
613
- def consistent?(other, subst)
614
- @type.consistent?(other, subst)
637
+ def consistent?(_other)
638
+ raise "should not called"
615
639
  end
616
640
  end
617
641
 
@@ -736,66 +760,74 @@ module TypeProf
736
760
  end
737
761
  end
738
762
 
739
- # Arguments for callee side
740
- class FormalArguments
763
+ class Signature
741
764
  include Utils::StructuralEquality
742
765
 
743
- def initialize(lead_tys, opt_tys, rest_ty, post_tys, kw_tys, kw_rest_ty, blk_ty)
744
- @lead_tys = lead_tys
745
- @opt_tys = opt_tys
746
- @rest_ty = rest_ty
747
- @post_tys = post_tys
748
- @kw_tys = kw_tys
749
- kw_tys.each {|a| raise if a.size != 3 } if kw_tys
750
- @kw_rest_ty = kw_rest_ty
751
- @blk_ty = blk_ty
752
- end
753
-
754
- attr_reader :lead_tys, :opt_tys, :rest_ty, :post_tys, :kw_tys, :kw_rest_ty, :blk_ty
755
-
756
- def consistent?(fargs, subst)
757
- warn "used?"
758
- return false if @lead_tys.size != fargs.lead_tys.size
759
- return false unless @lead_tys.zip(fargs.lead_tys).all? {|ty1, ty2| ty1.consistent?(ty2, subst) }
760
- return false if (@opt_tys || []) != (fargs.opt_tys || []) # ??
761
- if @rest_ty
762
- return false unless @rest_ty.consistent?(fargs.rest_ty, subst)
763
- end
764
- if @post_tys
765
- return false if @post_tys.size != fargs.post_tys.size
766
- return false unless @post_tys.zip(fargs.post_tys).all? {|ty1, ty2| ty1.consistent?(ty2, subst) }
767
- end
768
- return false if @kw_tys.size != fargs.kw_tys.size
769
- return false unless @kw_tys.zip(fargs.kw_tys).all? {|(_, ty1), (_, ty2)| ty1.consistent?(ty2, subst) }
770
- if @kw_rest_ty
771
- return false unless @kw_rest_ty.consistent?(fargs.kw_rest_ty, subst)
772
- end
773
- # intentionally skip blk_ty
774
- true
775
- end
776
-
777
766
  def screen_name(scratch)
778
- fargs = @lead_tys.map {|ty| ty.screen_name(scratch) }
767
+ str = @lead_tys.map {|ty| ty.screen_name(scratch) }
779
768
  if @opt_tys
780
- fargs += @opt_tys.map {|ty| "?" + ty.screen_name(scratch) }
769
+ str += @opt_tys.map {|ty| "?" + ty.screen_name(scratch) }
781
770
  end
782
771
  if @rest_ty
783
- fargs << ("*" + @rest_ty.screen_name(scratch))
772
+ str << ("*" + @rest_ty.screen_name(scratch))
784
773
  end
785
774
  if @post_tys
786
- fargs += @post_tys.map {|ty| ty.screen_name(scratch) }
775
+ str += @post_tys.map {|ty| ty.screen_name(scratch) }
787
776
  end
788
777
  if @kw_tys
789
778
  @kw_tys.each do |req, sym, ty|
790
779
  opt = req ? "" : "?"
791
- fargs << "#{ opt }#{ sym }: #{ ty.screen_name(scratch) }"
780
+ str << "#{ opt }#{ sym }: #{ ty.screen_name(scratch) }"
792
781
  end
793
782
  end
794
783
  if @kw_rest_ty
795
- fargs << ("**" + @kw_rest_ty.screen_name(scratch))
784
+ str << ("**" + @kw_rest_ty.screen_name(scratch))
785
+ end
786
+ str = str.empty? ? "" : "(#{ str.join(", ") })"
787
+
788
+ optional = false
789
+ blks = []
790
+ @blk_ty.each_child_global do |ty|
791
+ if ty.is_a?(Type::Proc)
792
+ blks << ty
793
+ else
794
+ # XXX: how should we handle types other than Type.nil
795
+ optional = true
796
+ end
796
797
  end
797
- # intentionally skip blk_ty
798
- fargs
798
+ if blks != []
799
+ str << " " if str != ""
800
+ str << "?" if optional
801
+ str << scratch.show_block_signature(blks)
802
+ end
803
+
804
+ str
805
+ end
806
+ end
807
+
808
+ class MethodSignature < Signature
809
+ def initialize(lead_tys, opt_tys, rest_ty, post_tys, kw_tys, kw_rest_ty, blk_ty)
810
+ @lead_tys = lead_tys
811
+ @opt_tys = opt_tys
812
+ @rest_ty = rest_ty
813
+ @post_tys = post_tys
814
+ @kw_tys = kw_tys
815
+ kw_tys.each {|a| raise if a.size != 3 } if kw_tys
816
+ @kw_rest_ty = kw_rest_ty
817
+ @blk_ty = blk_ty
818
+ end
819
+
820
+ attr_reader :lead_tys, :opt_tys, :rest_ty, :post_tys, :kw_tys, :kw_rest_ty, :blk_ty
821
+
822
+ def substitute(subst, depth)
823
+ lead_tys = @lead_tys.map {|ty| ty.substitute(subst, depth - 1) }
824
+ opt_tys = @opt_tys.map {|ty| ty.substitute(subst, depth - 1) }
825
+ rest_ty = @rest_ty&.substitute(subst, depth - 1)
826
+ post_tys = @post_tys.map {|ty| ty.substitute(subst, depth - 1) }
827
+ kw_tys = @kw_tys.map {|req, key, ty| [req, key, ty.substitute(subst, depth - 1)] }
828
+ kw_rest_ty = @kw_rest_ty&.substitute(subst, depth - 1)
829
+ blk_ty = @blk_ty.substitute(subst, depth - 1)
830
+ MethodSignature.new(lead_tys, opt_tys, rest_ty, post_tys, kw_tys, kw_rest_ty, blk_ty)
799
831
  end
800
832
 
801
833
  def merge(other)
@@ -810,8 +842,6 @@ module TypeProf
810
842
  raise if !!kws1[kw] != !!kws2[kw]
811
843
  end
812
844
  elsif @kw_tys || other.kw_tys
813
- puts
814
- p self, other
815
845
  (@kw_tys || other.kw_tys).each do |req,|
816
846
  raise if req
817
847
  end
@@ -859,231 +889,60 @@ module TypeProf
859
889
  end
860
890
  end
861
891
  blk_ty = @blk_ty.union(other.blk_ty) if @blk_ty
862
- FormalArguments.new(lead_tys, opt_tys, rest_ty, post_tys, kw_tys, kw_rest_ty, blk_ty)
892
+ MethodSignature.new(lead_tys, opt_tys, rest_ty, post_tys, kw_tys, kw_rest_ty, blk_ty)
863
893
  end
864
894
  end
865
895
 
866
- # Arguments from caller side
867
- class ActualArguments
868
- def initialize(lead_tys, rest_ty, kw_ty, blk_ty)
896
+ class BlockSignature < Signature
897
+ def initialize(lead_tys, opt_tys, rest_ty, blk_ty)
869
898
  @lead_tys = lead_tys
899
+ @opt_tys = opt_tys
870
900
  @rest_ty = rest_ty
871
- @kw_ty = kw_ty
872
901
  @blk_ty = blk_ty
873
- raise unless blk_ty
874
- end
875
-
876
- attr_reader :lead_tys, :rest_ty, :kw_ty, :blk_ty
877
-
878
- def merge(aargs)
879
- len = [@lead_tys.size, aargs.lead_tys.size].min
880
- lead_tys = @lead_tys[0, len].zip(aargs.lead_tys[0, len]).map do |ty1, ty2|
881
- ty1.union(ty2)
882
- end
883
- rest_ty = @rest_ty || Type.bot
884
- rest_ty = rest_ty.union(aargs.rest_ty) if aargs.rest_ty
885
- (@lead_tys[len..] + aargs.lead_tys[len..]).each do |ty|
886
- rest_ty = rest_ty.union(ty)
887
- end
888
- rest_ty = nil if rest_ty == Type.bot
889
- #kw_ty = @kw_ty.union(aargs.kw_ty) # TODO
890
- blk_ty = @blk_ty.union(aargs.blk_ty)
891
- ActualArguments.new(lead_tys, rest_ty, kw_ty, blk_ty)
892
- end
893
-
894
- def globalize(caller_env, visited, depth)
895
- lead_tys = @lead_tys.map {|ty| ty.globalize(caller_env, visited, depth) }
896
- rest_ty = @rest_ty.globalize(caller_env, visited, depth) if @rest_ty
897
- kw_ty = @kw_ty.globalize(caller_env, visited, depth) if @kw_ty
898
- ActualArguments.new(lead_tys, rest_ty, kw_ty, @blk_ty)
899
- end
900
-
901
- def limit_size(limit)
902
- self
903
- end
904
-
905
- def each_formal_arguments(fargs_format)
906
- lead_num = fargs_format[:lead_num] || 0
907
- post_num = fargs_format[:post_num] || 0
908
- rest_acceptable = !!fargs_format[:rest_start]
909
- keyword = fargs_format[:keyword]
910
- kw_rest_acceptable = !!fargs_format[:kwrest]
911
- opt = fargs_format[:opt]
912
- #p fargs_format
913
-
914
- # TODO: expand tuples to normal arguments
915
-
916
- # check number of arguments
917
- if !@rest_ty && lead_num + post_num > @lead_tys.size
918
- # too less
919
- yield "wrong number of arguments (given #{ @lead_tys.size }, expected #{ lead_num + post_num })"
920
- return
921
- end
922
- if !rest_acceptable
923
- # too many
924
- if opt
925
- if lead_num + post_num + opt.size - 1 < @lead_tys.size
926
- yield "wrong number of arguments (given #{ @lead_tys.size }, expected #{ lead_num + post_num }..#{ lead_num + post_num + opt.size - 1})"
927
- return
928
- end
929
- else
930
- if lead_num + post_num < @lead_tys.size
931
- yield "wrong number of arguments (given #{ @lead_tys.size }, expected #{ lead_num + post_num })"
932
- return
902
+ # TODO: kw_tys
903
+ end
904
+
905
+ attr_reader :lead_tys, :opt_tys, :rest_ty, :blk_ty
906
+
907
+ def merge(bsig)
908
+ if @rest_ty && bsig.rest_ty
909
+ rest_ty = @rest_ty.union(bsig.rest_ty)
910
+ BlockSignature.new(@lead_tys, [], rest_ty, @blk_ty.union(bsig.blk_ty))
911
+ elsif @rest_ty || bsig.rest_ty
912
+ rest_ty = @rest_ty || bsig.rest_ty
913
+ rest_ty = @opt_tys.inject(rest_ty, &:union)
914
+ rest_ty = bsig.opt_tys.inject(rest_ty, &:union)
915
+
916
+ lead_tys = []
917
+ [@lead_tys.size, bsig.lead_tys.size].max.times do |i|
918
+ ty1 = @lead_tys[i]
919
+ ty2 = bsig.lead_tys[i]
920
+ if ty1 && ty2
921
+ lead_tys << ty1.union(ty2)
922
+ else
923
+ rest_ty = rest_ty.union(ty1 || ty2)
933
924
  end
934
925
  end
935
- end
936
926
 
937
- if @rest_ty
938
- lower_bound = [lead_num + post_num - @lead_tys.size, 0].max
939
- upper_bound = lead_num + post_num - @lead_tys.size + (opt ? opt.size - 1 : 0) + (rest_acceptable ? 1 : 0)
940
- rest_elem = @rest_ty.is_a?(Type::Array) ? @rest_ty.elems.squash : Type.any
927
+ BlockSignature.new(lead_tys, [], rest_ty, @blk_ty.union(bsig.blk_ty))
941
928
  else
942
- lower_bound = upper_bound = 0
943
- end
944
-
945
- if keyword
946
- kw_tys = []
947
- keyword.each do |kw|
948
- case
949
- when kw.is_a?(Symbol) # required keyword
950
- key = kw
951
- req = true
952
- when kw.size == 2 # optional keyword (default value is a literal)
953
- key, default_ty = *kw
954
- default_ty = Type.guess_literal_type(default_ty)
955
- default_ty = default_ty.type if default_ty.is_a?(Type::Literal)
956
- req = false
957
- else # optional keyword (default value is an expression)
958
- key, = kw
959
- req = false
960
- end
961
-
962
- sym = Type::Symbol.new(key, Type::Instance.new(Type::Builtin[:sym]))
963
- ty = Type.bot
964
- if @kw_ty.is_a?(Type::Hash)
965
- # XXX: consider Union
966
- ty = @kw_ty.elems[sym]
967
- # XXX: remove the key
968
- end
969
- if ty == Type.bot
970
- yield "no argument for required keywords"
971
- return
972
- end
973
- ty = ty.union(default_ty) if default_ty
974
- kw_tys << [req, key, ty]
975
- end
976
- end
977
- if kw_rest_acceptable
978
- kw_rest_ty = @kw_ty
979
- if kw_rest_ty == Type.any
980
- kw_rest_ty = Type.gen_hash {|h| h[Type.any] = Type.any }
981
- end
982
- end
983
- #if @kw_ty
984
- # yield "passed a keyword to non-keyword method"
985
- #end
986
-
987
- (lower_bound .. upper_bound).each do |rest_len|
988
- aargs = @lead_tys + [rest_elem] * rest_len
989
- lead_tys = aargs.shift(lead_num)
990
- lead_tys << rest_elem until lead_tys.size == lead_num
991
- post_tys = aargs.pop(post_num)
992
- post_tys.unshift(rest_elem) until post_tys.size == post_num
993
- start_pc = 0
994
- if opt
995
- tmp_opt = opt[1..]
996
- opt_tys = []
997
- until aargs.empty? || tmp_opt.empty?
998
- opt_tys << aargs.shift
999
- start_pc = tmp_opt.shift
1000
- end
1001
- end
1002
- if rest_acceptable
1003
- acc = aargs.inject {|acc, ty| acc.union(ty) }
1004
- acc = acc ? acc.union(rest_elem) : rest_elem if rest_elem
1005
- acc ||= Type.bot
1006
- rest_ty = acc
1007
- aargs.clear
1008
- end
1009
- if !aargs.empty?
1010
- yield "wrong number of arguments (given #{ @lead_tys.size }, expected #{ lead_num + post_num })"
1011
- return
929
+ lead_tys = []
930
+ n = [@lead_tys.size, bsig.lead_tys.size].min
931
+ n.times do |i|
932
+ lead_tys << @lead_tys[i].union(bsig.lead_tys[i])
1012
933
  end
1013
- yield FormalArguments.new(lead_tys, opt_tys, rest_ty, post_tys, kw_tys, kw_rest_ty, @blk_ty), start_pc
1014
- end
1015
- end
1016
-
1017
- def consistent_with_formal_arguments?(fargs, subst)
1018
- aargs = @lead_tys.dup
1019
-
1020
- # aargs: lead_tys, rest_ty
1021
- # fargs: lead_tys, opt_tys, rest_ty, post_tys
1022
- if @rest_ty
1023
- lower_bound = [0, fargs.lead_tys.size + fargs.post_tys.size - aargs.size].max
1024
- upper_bound = [0, lower_bound + fargs.opt_tys.size].max
1025
- (lower_bound..upper_bound).each do |n|
1026
- tmp_aargs = ActualArguments.new(@lead_tys + [@rest_ty] * n, nil, @kw_ty, @blk_ty)
1027
- if tmp_aargs.consistent_with_formal_arguments?(fargs, subst)
1028
- return true
934
+ opt_tys1 = @lead_tys[n..] + @opt_tys
935
+ opt_tys2 = bsig.lead_tys[n..] + bsig.opt_tys
936
+ opt_tys = []
937
+ [opt_tys1.size, opt_tys2.size].max.times do |i|
938
+ if opt_tys1[i] && opt_tys2[i]
939
+ opt_tys << opt_tys1[i].union(opt_tys2[i])
940
+ else
941
+ opt_tys << (opt_tys1[i] || opt_tys2[i])
1029
942
  end
1030
943
  end
1031
- return false
1032
- end
1033
-
1034
- if fargs.rest_ty
1035
- return false if aargs.size < fargs.lead_tys.size + fargs.post_tys.size
1036
- aargs.shift(fargs.lead_tys.size).zip(fargs.lead_tys) do |aarg, farg|
1037
- return false unless aarg.consistent?(farg, subst)
1038
- end
1039
- aargs.pop(fargs.post_tys.size).zip(fargs.post_tys) do |aarg, farg|
1040
- return false unless aarg.consistent?(farg, subst)
1041
- end
1042
- fargs.opt_tys.each do |farg|
1043
- aarg = aargs.shift
1044
- return false unless aarg.consistent?(farg, subst)
1045
- end
1046
- aargs.each do |aarg|
1047
- return false unless aarg.consistent?(fargs.rest_ty, subst)
1048
- end
1049
- else
1050
- return false if aargs.size < fargs.lead_tys.size + fargs.post_tys.size
1051
- return false if aargs.size > fargs.lead_tys.size + fargs.post_tys.size + fargs.opt_tys.size
1052
- aargs.shift(fargs.lead_tys.size).zip(fargs.lead_tys) do |aarg, farg|
1053
- return false unless aarg.consistent?(farg, subst)
1054
- end
1055
- aargs.pop(fargs.post_tys.size).zip(fargs.post_tys) do |aarg, farg|
1056
- return false unless aarg.consistent?(farg, subst)
1057
- end
1058
- aargs.zip(fargs.opt_tys) do |aarg, farg|
1059
- return false unless aarg.consistent?(farg, subst)
1060
- end
1061
- end
1062
- # XXX: fargs.keyword_tys
1063
-
1064
- case fargs.blk_ty
1065
- when Type::TypedProc
1066
- return false if @blk_ty == Type.nil
1067
- when Type.nil
1068
- return false if @blk_ty != Type.nil
1069
- when Type::Any
1070
- else
1071
- raise "unknown typo of formal block signature"
1072
- end
1073
- true
1074
- end
1075
-
1076
- def screen_name(scratch)
1077
- aargs = @lead_tys.map {|ty| ty.screen_name(scratch) }
1078
- if @rest_ty
1079
- aargs << ("*" + @rest_ty.screen_name(scratch))
1080
- end
1081
- if @kw_ty
1082
- aargs << ("**" + @kw_ty.screen_name(scratch)) # TODO: Hash notation -> keyword notation
944
+ BlockSignature.new(lead_tys, opt_tys, nil, @blk_ty.union(bsig.blk_ty))
1083
945
  end
1084
- s = "(#{ aargs.join(", ") })"
1085
- s << " { #{ scratch.proc_screen_name(@blk_ty) } }" if @blk_ty != Type.nil
1086
- s
1087
946
  end
1088
947
  end
1089
948
  end