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.
Files changed (218) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/main.yml +26 -0
  3. data/.gitignore +7 -0
  4. data/.gitmodules +6 -0
  5. data/Gemfile +12 -0
  6. data/Gemfile.lock +41 -0
  7. data/README.md +53 -0
  8. data/Rakefile +10 -0
  9. data/doc/doc.ja.md +415 -0
  10. data/doc/doc.md +429 -0
  11. data/doc/ppl2019.pdf +0 -0
  12. data/exe/typeprof +5 -0
  13. data/lib/typeprof.rb +13 -0
  14. data/lib/typeprof/analyzer.rb +1911 -0
  15. data/lib/typeprof/builtin.rb +554 -0
  16. data/lib/typeprof/cli.rb +110 -0
  17. data/lib/typeprof/container-type.rb +626 -0
  18. data/lib/typeprof/export.rb +203 -0
  19. data/lib/typeprof/import.rb +546 -0
  20. data/lib/typeprof/insns-def.rb +61 -0
  21. data/lib/typeprof/iseq.rb +387 -0
  22. data/lib/typeprof/method.rb +267 -0
  23. data/lib/typeprof/type.rb +1092 -0
  24. data/lib/typeprof/utils.rb +209 -0
  25. data/run.sh +3 -0
  26. data/smoke/alias.rb +30 -0
  27. data/smoke/alias2.rb +19 -0
  28. data/smoke/any-cbase.rb +5 -0
  29. data/smoke/any1.rb +15 -0
  30. data/smoke/any2.rb +17 -0
  31. data/smoke/arguments.rb +16 -0
  32. data/smoke/array-each.rb +14 -0
  33. data/smoke/array-each2.rb +15 -0
  34. data/smoke/array-each3.rb +15 -0
  35. data/smoke/array-ltlt.rb +13 -0
  36. data/smoke/array-ltlt2.rb +16 -0
  37. data/smoke/array-map.rb +11 -0
  38. data/smoke/array-map2.rb +10 -0
  39. data/smoke/array-map3.rb +22 -0
  40. data/smoke/array-mul.rb +17 -0
  41. data/smoke/array-plus1.rb +10 -0
  42. data/smoke/array-plus2.rb +15 -0
  43. data/smoke/array-pop.rb +11 -0
  44. data/smoke/array-replace.rb +12 -0
  45. data/smoke/array-s-aref.rb +11 -0
  46. data/smoke/array1.rb +26 -0
  47. data/smoke/array10.rb +14 -0
  48. data/smoke/array11.rb +13 -0
  49. data/smoke/array12.rb +24 -0
  50. data/smoke/array13.rb +30 -0
  51. data/smoke/array14.rb +13 -0
  52. data/smoke/array2.rb +27 -0
  53. data/smoke/array3.rb +25 -0
  54. data/smoke/array4.rb +14 -0
  55. data/smoke/array5.rb +13 -0
  56. data/smoke/array6.rb +14 -0
  57. data/smoke/array7.rb +13 -0
  58. data/smoke/array8.rb +13 -0
  59. data/smoke/array9.rb +12 -0
  60. data/smoke/attr.rb +28 -0
  61. data/smoke/backtrace.rb +32 -0
  62. data/smoke/block1.rb +22 -0
  63. data/smoke/block10.rb +14 -0
  64. data/smoke/block11.rb +39 -0
  65. data/smoke/block12.rb +22 -0
  66. data/smoke/block2.rb +14 -0
  67. data/smoke/block3.rb +38 -0
  68. data/smoke/block4.rb +18 -0
  69. data/smoke/block5.rb +18 -0
  70. data/smoke/block6.rb +20 -0
  71. data/smoke/block7.rb +20 -0
  72. data/smoke/block8.rb +27 -0
  73. data/smoke/block9.rb +12 -0
  74. data/smoke/blown.rb +12 -0
  75. data/smoke/break1.rb +18 -0
  76. data/smoke/break2.rb +15 -0
  77. data/smoke/case.rb +16 -0
  78. data/smoke/case2.rb +17 -0
  79. data/smoke/class.rb +5 -0
  80. data/smoke/class_instance_var.rb +9 -0
  81. data/smoke/class_method.rb +25 -0
  82. data/smoke/class_method2.rb +21 -0
  83. data/smoke/class_method3.rb +27 -0
  84. data/smoke/constant1.rb +38 -0
  85. data/smoke/constant2.rb +33 -0
  86. data/smoke/constant3.rb +9 -0
  87. data/smoke/constant4.rb +11 -0
  88. data/smoke/context-sensitive1.rb +12 -0
  89. data/smoke/cvar.rb +28 -0
  90. data/smoke/cvar2.rb +17 -0
  91. data/smoke/demo.rb +80 -0
  92. data/smoke/demo1.rb +16 -0
  93. data/smoke/demo10.rb +20 -0
  94. data/smoke/demo11.rb +11 -0
  95. data/smoke/demo2.rb +14 -0
  96. data/smoke/demo3.rb +16 -0
  97. data/smoke/demo4.rb +27 -0
  98. data/smoke/demo5.rb +13 -0
  99. data/smoke/demo6.rb +21 -0
  100. data/smoke/demo7.rb +14 -0
  101. data/smoke/demo8.rb +18 -0
  102. data/smoke/demo9.rb +18 -0
  103. data/smoke/dummy-execution1.rb +14 -0
  104. data/smoke/dummy-execution2.rb +16 -0
  105. data/smoke/ensure1.rb +20 -0
  106. data/smoke/enumerator.rb +15 -0
  107. data/smoke/expandarray1.rb +22 -0
  108. data/smoke/expandarray2.rb +23 -0
  109. data/smoke/fib.rb +28 -0
  110. data/smoke/flow1.rb +16 -0
  111. data/smoke/flow2.rb +14 -0
  112. data/smoke/flow3.rb +14 -0
  113. data/smoke/flow4.rb +5 -0
  114. data/smoke/flow5.rb +19 -0
  115. data/smoke/flow6.rb +19 -0
  116. data/smoke/flow7.rb +26 -0
  117. data/smoke/for.rb +9 -0
  118. data/smoke/freeze.rb +11 -0
  119. data/smoke/function.rb +16 -0
  120. data/smoke/gvar.rb +13 -0
  121. data/smoke/hash-fetch.rb +27 -0
  122. data/smoke/hash1.rb +18 -0
  123. data/smoke/hash2.rb +12 -0
  124. data/smoke/hash3.rb +13 -0
  125. data/smoke/hash4.rb +10 -0
  126. data/smoke/hash5.rb +14 -0
  127. data/smoke/inheritance.rb +34 -0
  128. data/smoke/inheritance2.rb +29 -0
  129. data/smoke/initialize.rb +26 -0
  130. data/smoke/instance_eval.rb +18 -0
  131. data/smoke/int_times.rb +14 -0
  132. data/smoke/integer.rb +10 -0
  133. data/smoke/ivar.rb +29 -0
  134. data/smoke/ivar2.rb +30 -0
  135. data/smoke/kernel-class.rb +12 -0
  136. data/smoke/keyword1.rb +11 -0
  137. data/smoke/keyword2.rb +11 -0
  138. data/smoke/keyword3.rb +12 -0
  139. data/smoke/keyword4.rb +11 -0
  140. data/smoke/keyword5.rb +15 -0
  141. data/smoke/kwsplat1.rb +42 -0
  142. data/smoke/kwsplat2.rb +12 -0
  143. data/smoke/manual-rbs.rb +27 -0
  144. data/smoke/manual-rbs.rbs +3 -0
  145. data/smoke/manual-rbs2.rb +20 -0
  146. data/smoke/manual-rbs2.rbs +8 -0
  147. data/smoke/masgn1.rb +13 -0
  148. data/smoke/masgn2.rb +17 -0
  149. data/smoke/masgn3.rb +12 -0
  150. data/smoke/method_in_branch.rb +22 -0
  151. data/smoke/module1.rb +29 -0
  152. data/smoke/module2.rb +28 -0
  153. data/smoke/module3.rb +33 -0
  154. data/smoke/module4.rb +29 -0
  155. data/smoke/module_function1.rb +28 -0
  156. data/smoke/module_function2.rb +28 -0
  157. data/smoke/multiple-include.rb +14 -0
  158. data/smoke/multiple-superclass.rb +16 -0
  159. data/smoke/next1.rb +20 -0
  160. data/smoke/next2.rb +16 -0
  161. data/smoke/object-send1.rb +22 -0
  162. data/smoke/once.rb +12 -0
  163. data/smoke/optional1.rb +13 -0
  164. data/smoke/optional2.rb +15 -0
  165. data/smoke/parameterizedd-self.rb +18 -0
  166. data/smoke/pathname1.rb +13 -0
  167. data/smoke/pathname2.rb +13 -0
  168. data/smoke/printf.rb +20 -0
  169. data/smoke/proc.rb +19 -0
  170. data/smoke/proc2.rb +16 -0
  171. data/smoke/proc3.rb +14 -0
  172. data/smoke/proc4.rb +11 -0
  173. data/smoke/range.rb +13 -0
  174. data/smoke/redo1.rb +21 -0
  175. data/smoke/redo2.rb +22 -0
  176. data/smoke/req-keyword.rb +12 -0
  177. data/smoke/rescue1.rb +20 -0
  178. data/smoke/rescue2.rb +22 -0
  179. data/smoke/respond_to.rb +22 -0
  180. data/smoke/rest-farg.rb +10 -0
  181. data/smoke/rest1.rb +25 -0
  182. data/smoke/rest2.rb +30 -0
  183. data/smoke/rest3.rb +36 -0
  184. data/smoke/rest4.rb +18 -0
  185. data/smoke/rest5.rb +10 -0
  186. data/smoke/rest6.rb +11 -0
  187. data/smoke/retry1.rb +20 -0
  188. data/smoke/return.rb +13 -0
  189. data/smoke/reveal.rb +13 -0
  190. data/smoke/singleton_class.rb +8 -0
  191. data/smoke/singleton_method.rb +9 -0
  192. data/smoke/step.rb +17 -0
  193. data/smoke/string-split.rb +11 -0
  194. data/smoke/struct.rb +9 -0
  195. data/smoke/struct2.rb +24 -0
  196. data/smoke/super1.rb +50 -0
  197. data/smoke/super2.rb +16 -0
  198. data/smoke/super3.rb +19 -0
  199. data/smoke/svar1.rb +12 -0
  200. data/smoke/tap1.rb +17 -0
  201. data/smoke/toplevel.rb +12 -0
  202. data/smoke/two-map.rb +17 -0
  203. data/smoke/type_var.rb +10 -0
  204. data/smoke/typed_method.rb +15 -0
  205. data/smoke/union-recv.rb +29 -0
  206. data/smoke/variadic1.rb.notyet +5 -0
  207. data/smoke/wrong-extend.rb +25 -0
  208. data/smoke/wrong-include.rb +26 -0
  209. data/smoke/wrong-rbs.rb +15 -0
  210. data/smoke/wrong-rbs.rbs +7 -0
  211. data/testbed/ao.rb +297 -0
  212. data/testbed/diff-lcs-entrypoint.rb +4 -0
  213. data/testbed/goodcheck-Gemfile.lock +51 -0
  214. data/tools/coverage.rb +14 -0
  215. data/tools/setup-insns-def.rb +30 -0
  216. data/tools/stackprof-wrapper.rb +10 -0
  217. data/typeprof.gemspec +24 -0
  218. metadata +262 -0
@@ -0,0 +1,110 @@
1
+ require "optparse"
2
+
3
+ module TypeProf
4
+ class CLI
5
+ def initialize(argv)
6
+ opt = OptionParser.new
7
+
8
+ @output = nil
9
+
10
+ # Verbose level:
11
+ # * 0: no output
12
+ # * 1: show indicator
13
+ # * 2: debug print
14
+ @verbose = 1
15
+
16
+ @options = {
17
+ type_depth_limit: 5,
18
+ pedantic_output: false,
19
+ show_errors: false,
20
+ }
21
+
22
+ opt.on("-o OUTFILE") {|v| @output = v }
23
+ opt.on("-q", "--quiet") {|v| @verbose = 0 }
24
+ opt.on("-v", "--verbose") {|v| @verbose = 2 }
25
+ opt.on("-f OPTION") do |v|
26
+ key, args = v.split("=", 2)
27
+ case key
28
+ when "type-depth-limit"
29
+ @options[:type_depth_limit] = Integer(args)
30
+ when "pedantic-output"
31
+ @options[:pedantic_output] = true
32
+ when "show-errors"
33
+ @options[:show_errors] = true
34
+ when "show-container-raw-elements"
35
+ @options[:show_container_raw_elements] = true
36
+ else
37
+ raise OptionParser::InvalidOption.new("unknown option: #{ key }")
38
+ end
39
+ end
40
+
41
+ opt.parse!(argv)
42
+
43
+ @rb_files = []
44
+ @rbs_files = []
45
+ argv.each do |path|
46
+ if File.extname(path) == ".rbs"
47
+ @rbs_files << path
48
+ else
49
+ @rb_files << path
50
+ end
51
+ end
52
+
53
+ raise OptionParser::InvalidOption.new("no input files") if @rb_files.empty?
54
+
55
+ TypeProf.const_set(:Config, self)
56
+
57
+ rescue OptionParser::InvalidOption
58
+ puts $!
59
+ exit
60
+ end
61
+
62
+ attr_reader :verbose, :options, :files
63
+ attr_accessor :output
64
+
65
+ def run
66
+ scratch = Scratch.new
67
+ Builtin.setup_initial_global_env(scratch)
68
+
69
+ prologue_ctx = Context.new(nil, nil, nil)
70
+ prologue_ep = ExecutionPoint.new(prologue_ctx, -1, nil)
71
+ prologue_env = Env.new(StaticEnv.new(:top, Type.nil, false), [], [], Utils::HashWrapper.new({}))
72
+
73
+ @rb_files.each do |path|
74
+ if path == "-"
75
+ iseq = ISeq.compile_str($<.read)
76
+ else
77
+ iseq = ISeq.compile(path)
78
+ end
79
+ ep, env = CLI.starting_state(iseq)
80
+ scratch.merge_env(ep, env)
81
+ scratch.add_callsite!(ep.ctx, nil, prologue_ep, prologue_env) {|ty, ep| }
82
+ end
83
+
84
+ @rbs_files.each do |path|
85
+ RubySignatureImporter.import_rbs_file(scratch, path)
86
+ end
87
+
88
+ result = scratch.type_profile
89
+
90
+ if @output
91
+ open(@output, "w") do |output|
92
+ scratch.report(result, output)
93
+ end
94
+ else
95
+ scratch.report(result, $stdout)
96
+ end
97
+ end
98
+
99
+ def self.starting_state(iseq)
100
+ cref = CRef.new(:bottom, Type::Builtin[:obj], false) # object
101
+ recv = Type::Instance.new(Type::Builtin[:obj])
102
+ ctx = Context.new(iseq, cref, nil)
103
+ ep = ExecutionPoint.new(ctx, 0, nil)
104
+ locals = [Type.nil] * iseq.locals.size
105
+ env = Env.new(StaticEnv.new(recv, Type.nil, false), locals, [], Utils::HashWrapper.new({}))
106
+
107
+ return ep, env
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,626 @@
1
+ module TypeProf
2
+ class AllocationSite
3
+ include Utils::StructuralEquality
4
+
5
+ def initialize(val, parent = nil)
6
+ raise if !val.is_a?(Utils::StructuralEquality) && !val.is_a?(Integer) && !val.is_a?(Symbol)
7
+ @val = val
8
+ @parent = parent
9
+ @_hash ||= (@val.hash ^ @parent.hash)
10
+ end
11
+
12
+ attr_reader :val, :parent
13
+
14
+ def hash
15
+ @_hash
16
+ end
17
+
18
+ def add_id(val)
19
+ AllocationSite.new(val, self)
20
+ end
21
+ end
22
+
23
+ class Type # or AbstractValue
24
+ # This is a type for global interface, e.g., TypedISeq.
25
+ # Do not insert Array type to local environment, stack, etc.
26
+ class Array < Type
27
+ def initialize(elems, base_type)
28
+ raise unless elems.is_a?(Array::Elements)
29
+ @elems = elems # Array::Elements
30
+ raise unless base_type
31
+ @base_type = base_type
32
+ # XXX: need infinite recursion
33
+ end
34
+
35
+ attr_reader :elems, :base_type
36
+
37
+ def inspect
38
+ "Type::Array[#{ @elems.inspect }, base_type: #{ @base_type.inspect }]"
39
+ #@base_type.inspect
40
+ end
41
+
42
+ def screen_name(scratch)
43
+ str = @elems.screen_name(scratch)
44
+ if str.start_with?("*")
45
+ str = @base_type.screen_name(scratch) + str[1..]
46
+ end
47
+ str
48
+ end
49
+
50
+ def globalize(env, visited, depth)
51
+ return Type.any if depth <= 0
52
+ elems = @elems.globalize(env, visited, depth - 1)
53
+ base_ty = @base_type.globalize(env, visited, depth - 1)
54
+ Array.new(elems, base_ty)
55
+ end
56
+
57
+ def localize(env, alloc_site, depth)
58
+ return env, Type.any if depth <= 0
59
+ alloc_site = alloc_site.add_id(:ary)
60
+ env, elems = @elems.localize(env, alloc_site, depth - 1)
61
+ env.deploy_array_type(alloc_site, elems, @base_type)
62
+ end
63
+
64
+ def limit_size(limit)
65
+ return Type.any if limit <= 0
66
+ Array.new(@elems.limit_size(limit - 1), @base_type)
67
+ end
68
+
69
+ def get_method(mid, scratch)
70
+ raise
71
+ end
72
+
73
+ def consistent?(other, subst)
74
+ case other
75
+ when Type::Any then true
76
+ when Type::Var then other.add_subst!(self, subst)
77
+ when Type::Union
78
+ other.types.each do |ty2|
79
+ return true if consistent?(ty2, subst)
80
+ end
81
+ when Type::Array
82
+ @base_type.consistent?(other.base_type, subst) && @elems.consistent?(other.elems, subst)
83
+ else
84
+ self == other
85
+ end
86
+ end
87
+
88
+ def substitute(subst, depth)
89
+ return Type.any if depth <= 0
90
+ elems = @elems.substitute(subst, depth - 1)
91
+ Array.new(elems, @base_type)
92
+ end
93
+
94
+ class Elements
95
+ include Utils::StructuralEquality
96
+
97
+ def initialize(lead_tys, rest_ty = Type.bot)
98
+ raise unless lead_tys.all? {|ty| ty.is_a?(Type) }
99
+ raise unless rest_ty.is_a?(Type)
100
+ @lead_tys, @rest_ty = lead_tys, rest_ty
101
+ end
102
+
103
+ attr_reader :lead_tys, :rest_ty
104
+
105
+ def to_local_type(id)
106
+ base_ty = Type::Instance.new(Type::Builtin[:ary])
107
+ Type::LocalArray.new(id, base_ty)
108
+ end
109
+
110
+ def globalize(env, visited, depth)
111
+ lead_tys = []
112
+ @lead_tys.each do |ty|
113
+ lead_tys << ty.globalize(env, visited, depth)
114
+ end
115
+ rest_ty = @rest_ty&.globalize(env, visited, depth)
116
+ Elements.new(lead_tys, rest_ty)
117
+ end
118
+
119
+ def localize(env, alloc_site, depth)
120
+ lead_tys = @lead_tys.map.with_index do |ty, i|
121
+ alloc_site2 = alloc_site.add_id(i)
122
+ env, ty = ty.localize(env, alloc_site2, depth)
123
+ ty
124
+ end
125
+ alloc_site_rest = alloc_site.add_id(:rest)
126
+ env, rest_ty = @rest_ty.localize(env, alloc_site_rest, depth)
127
+ return env, Elements.new(lead_tys, rest_ty)
128
+ end
129
+
130
+ def limit_size(limit)
131
+ Elements.new(@lead_tys.map {|ty| ty.limit_size(limit) }, @rest_ty.limit_size(limit))
132
+ end
133
+
134
+ def screen_name(scratch)
135
+ if Config.options[:show_container_raw_elements] || @rest_ty == Type.bot
136
+ s = @lead_tys.map do |ty|
137
+ ty.screen_name(scratch)
138
+ end
139
+ s << "*" + @rest_ty.screen_name(scratch) if @rest_ty != Type.bot
140
+ return "[#{ s.join(", ") }]"
141
+ end
142
+
143
+ "*[#{ squash.screen_name(scratch) }]"
144
+ end
145
+
146
+ def pretty_print(q)
147
+ q.group(9, "Elements[", "]") do
148
+ q.seplist(@lead_tys + [@rest_ty]) do |elem|
149
+ q.pp elem
150
+ end
151
+ end
152
+ end
153
+
154
+ def consistent?(other, subst)
155
+ n = [@lead_tys.size, other.lead_tys.size].min
156
+ n.times do |i|
157
+ return false unless @lead_tys[i].consistent?(other.lead_tys[i], subst)
158
+ end
159
+ rest_ty1 = @lead_tys[n..].inject(@rest_ty) {|ty1, ty2| ty1.union(ty2) }
160
+ rest_ty2 = other.lead_tys[n..].inject(other.rest_ty) {|ty1, ty2| ty1.union(ty2) }
161
+ rest_ty1.consistent?(rest_ty2, subst)
162
+ end
163
+
164
+ def substitute(subst, depth)
165
+ lead_tys = @lead_tys.map {|ty| ty.substitute(subst, depth) }
166
+ rest_ty = @rest_ty.substitute(subst, depth)
167
+ Elements.new(lead_tys, rest_ty)
168
+ end
169
+
170
+ def squash
171
+ @lead_tys.inject(@rest_ty) {|ty1, ty2| ty1.union(ty2) } #.union(Type.nil) # is this needed?
172
+ end
173
+
174
+ def [](idx)
175
+ if idx >= 0
176
+ if idx < @lead_tys.size
177
+ @lead_tys[idx]
178
+ elsif @rest_ty == Type.bot
179
+ Type.nil
180
+ else
181
+ @rest_ty
182
+ end
183
+ else
184
+ i = @lead_tys.size + idx
185
+ i = [i, 0].max
186
+ ty = @rest_ty
187
+ @lead_tys[i..].each do |ty2|
188
+ ty = ty.union(ty2)
189
+ end
190
+ ty
191
+ end
192
+ end
193
+
194
+ def update(idx, ty)
195
+ if idx
196
+ if idx >= 0
197
+ if idx < @lead_tys.size
198
+ lead_tys = Utils.array_update(@lead_tys, idx, ty)
199
+ Elements.new(lead_tys, @rest_ty)
200
+ else
201
+ rest_ty = @rest_ty.union(ty)
202
+ Elements.new(@lead_tys, rest_ty)
203
+ end
204
+ else
205
+ i = @lead_tys.size + idx
206
+ if @rest_ty == Type.bot
207
+ if i >= 0
208
+ lead_tys = Utils.array_update(@lead_tys, i, ty)
209
+ Elements.new(lead_tys, Type.bot)
210
+ else
211
+ # TODO: out of bound? should we emit an error?
212
+ Elements.new(@lead_tys, Type.bot)
213
+ end
214
+ else
215
+ i = [i, 0].max
216
+ lead_tys = @lead_tys[0, i] + @lead_tys[i..].map {|ty2| ty2.union(ty) }
217
+ rest_ty = @rest_ty.union(ty)
218
+ Elements.new(@lead_tys, rest_ty)
219
+ end
220
+ end
221
+ else
222
+ lead_tys = @lead_tys.map {|ty1| ty1.union(ty) }
223
+ rest_ty = @rest_ty.union(ty)
224
+ Elements.new(lead_tys, rest_ty)
225
+ end
226
+ end
227
+
228
+ def append(ty)
229
+ if @rest_ty == Type.bot
230
+ if @lead_tys.size < 5 # XXX: should be configurable, or ...?
231
+ lead_tys = @lead_tys + [ty]
232
+ Elements.new(lead_tys, @rest_ty)
233
+ else
234
+ Elements.new(@lead_tys, ty)
235
+ end
236
+ else
237
+ Elements.new(@lead_tys, @rest_ty.union(ty))
238
+ end
239
+ end
240
+
241
+ def union(other)
242
+ return self if self == other
243
+ raise "Hash::Elements merge Array::Elements" if other.is_a?(Hash::Elements)
244
+
245
+ lead_count = [@lead_tys.size, other.lead_tys.size].min
246
+ lead_tys = (0...lead_count).map do |i|
247
+ @lead_tys[i].union(other.lead_tys[i])
248
+ end
249
+
250
+ rest_ty = @rest_ty.union(other.rest_ty)
251
+ (@lead_tys[lead_count..-1] + other.lead_tys[lead_count..-1]).each do |ty|
252
+ rest_ty = rest_ty.union(ty)
253
+ end
254
+
255
+ Elements.new(lead_tys, rest_ty)
256
+ end
257
+
258
+ def take_first(num)
259
+ base_ty = Type::Instance.new(Type::Builtin[:ary])
260
+ if @lead_tys.size >= num
261
+ lead_tys = @lead_tys[0, num]
262
+ rest_ary_ty = Array.new(Elements.new(@lead_tys[num..-1], @rest_ty), base_ty)
263
+ return lead_tys, rest_ary_ty
264
+ else
265
+ lead_tys = @lead_tys.dup
266
+ until lead_tys.size == num
267
+ # .union(Type.nil) is needed for `a, b, c = [42]` to assign nil to b and c
268
+ lead_tys << @rest_ty.union(Type.nil)
269
+ end
270
+ rest_ary_ty = Array.new(Elements.new([], @rest_ty), base_ty)
271
+ return lead_tys, rest_ary_ty
272
+ end
273
+ end
274
+
275
+ def take_last(num)
276
+ base_ty = Type::Instance.new(Type::Builtin[:ary])
277
+ if @rest_ty == Type.bot
278
+ if @lead_tys.size >= num
279
+ following_tys = @lead_tys[-num, num]
280
+ rest_ary_ty = Array.new(Elements.new(@lead_tys[0...-num], Type.bot), base_ty)
281
+ return rest_ary_ty, following_tys
282
+ else
283
+ following_tys = @lead_tys[-num, num] || []
284
+ until following_tys.size == num
285
+ following_tys.unshift(Type.nil)
286
+ end
287
+ rest_ary_ty = Array.new(Elements.new([], Type.bot), base_ty)
288
+ return rest_ary_ty, following_tys
289
+ end
290
+ else
291
+ lead_tys = @lead_tys.dup
292
+ last_ty = rest_ty
293
+ following_tys = []
294
+ until following_tys.size == num
295
+ last_ty = last_ty.union(lead_tys.pop) unless lead_tys.empty?
296
+ following_tys.unshift(last_ty)
297
+ end
298
+ rest_ty = lead_tys.inject(last_ty) {|ty1, ty2| ty1.union(ty2) }
299
+ rest_ary_ty = Array.new(Elements.new([], rest_ty), base_ty)
300
+ return rest_ary_ty, following_tys
301
+ end
302
+ end
303
+ end
304
+ end
305
+
306
+ # Do not insert Array type to local environment, stack, etc.
307
+ class LocalArray < Type
308
+ def initialize(id, base_type)
309
+ @id = id
310
+ raise unless base_type
311
+ @base_type = base_type
312
+ end
313
+
314
+ attr_reader :id, :base_type
315
+
316
+ def inspect
317
+ "Type::LocalArray[#{ @id }, base_type: #{ @base_type.inspect }]"
318
+ end
319
+
320
+ def screen_name(scratch)
321
+ #raise "LocalArray must not be included in signature"
322
+ "LocalArray!"
323
+ end
324
+
325
+ def globalize(env, visited, depth)
326
+ if visited[self] || depth <= 0
327
+ Type.any
328
+ else
329
+ visited[self] = true
330
+ elems = env.get_container_elem_types(@id)
331
+ if elems
332
+ elems = elems.globalize(env, visited, depth - 1)
333
+ else
334
+ # TODO: currently out-of-scope array cannot be accessed
335
+ elems = Array::Elements.new([], Type.any)
336
+ end
337
+ Array.new(elems, @base_type)
338
+ end
339
+ end
340
+
341
+ def get_method(mid, scratch)
342
+ @base_type.get_method(mid, scratch)
343
+ end
344
+
345
+ def consistent?(other, subst)
346
+ raise "must not be used"
347
+ end
348
+ end
349
+
350
+
351
+ class Hash < Type
352
+ def initialize(elems, base_type)
353
+ @elems = elems
354
+ raise unless elems
355
+ @base_type = base_type
356
+ end
357
+
358
+ attr_reader :elems, :base_type
359
+
360
+ def inspect
361
+ "Type::Hash#{ @elems.inspect }"
362
+ end
363
+
364
+ def screen_name(scratch)
365
+ @elems.screen_name(scratch)
366
+ end
367
+
368
+ def globalize(env, visited, depth)
369
+ return Type.any if depth <= 0
370
+ elems = @elems.globalize(env, visited, depth - 1)
371
+ base_ty = @base_type.globalize(env, visited, depth - 1)
372
+ Hash.new(elems, base_ty)
373
+ end
374
+
375
+ def localize(env, alloc_site, depth)
376
+ return env, Type.any if depth <= 0
377
+ alloc_site = alloc_site.add_id(:hash)
378
+ env, elems = @elems.localize(env, alloc_site, depth - 1)
379
+ env.deploy_hash_type(alloc_site, elems, @base_type)
380
+ end
381
+
382
+ def limit_size(limit)
383
+ return Type.any if limit <= 0
384
+ Hash.new(@elems.limit_size(limit - 1), @base_type)
385
+ end
386
+
387
+ def get_method(mid, scratch)
388
+ raise
389
+ end
390
+
391
+ def consistent?(other, subst)
392
+ case other
393
+ when Type::Any then true
394
+ when Type::Var then other.add_subst!(self, subst)
395
+ when Type::Union
396
+ other.types.each do |ty2|
397
+ return true if consistent?(ty2, subst)
398
+ end
399
+ when Type::Hash
400
+ @base_type.consistent?(other.base_type, subst) && @elems.consistent?(other.elems, subst)
401
+ else
402
+ self == other
403
+ end
404
+ end
405
+
406
+ def substitute(subst, depth)
407
+ return Type.any if depth <= 0
408
+ elems = @elems.substitute(subst, depth - 1)
409
+ Hash.new(elems, @base_type)
410
+ end
411
+
412
+ class Elements
413
+ include Utils::StructuralEquality
414
+
415
+ def initialize(map_tys)
416
+ map_tys.each do |k_ty, v_ty|
417
+ raise unless k_ty.is_a?(Type)
418
+ raise unless v_ty.is_a?(Type)
419
+ raise if k_ty.is_a?(Type::Union)
420
+ raise if k_ty.is_a?(Type::LocalArray)
421
+ raise if k_ty.is_a?(Type::LocalHash)
422
+ raise if k_ty.is_a?(Type::Array)
423
+ raise if k_ty.is_a?(Type::Hash)
424
+ end
425
+ @map_tys = map_tys
426
+ end
427
+
428
+ attr_reader :map_tys
429
+
430
+ def to_local_type(id)
431
+ base_ty = Type::Instance.new(Type::Builtin[:hash])
432
+ Type::LocalHash.new(id, base_ty)
433
+ end
434
+
435
+ def globalize(env, visited, depth)
436
+ map_tys = {}
437
+ @map_tys.each do |k_ty, v_ty|
438
+ v_ty = v_ty.globalize(env, visited, depth)
439
+ if map_tys[k_ty]
440
+ map_tys[k_ty] = map_tys[k_ty].union(v_ty)
441
+ else
442
+ map_tys[k_ty] = v_ty
443
+ end
444
+ end
445
+ Elements.new(map_tys)
446
+ end
447
+
448
+ def localize(env, alloc_site, depth)
449
+ map_tys = @map_tys.to_h do |k_ty, v_ty|
450
+ alloc_site2 = alloc_site.add_id(k_ty)
451
+ env, v_ty = v_ty.localize(env, alloc_site2, depth)
452
+ [k_ty, v_ty]
453
+ end
454
+ return env, Elements.new(map_tys)
455
+ end
456
+
457
+ def limit_size(limit)
458
+ map_tys = {}
459
+ @map_tys.each do |k_ty, v_ty|
460
+ k_ty = k_ty.limit_size(limit)
461
+ v_ty = v_ty.limit_size(limit)
462
+ if map_tys[k_ty]
463
+ map_tys[k_ty] = map_tys[k_ty].union(v_ty)
464
+ else
465
+ map_tys[k_ty] = v_ty
466
+ end
467
+ end
468
+ Elements.new(map_tys)
469
+ end
470
+
471
+ def screen_name(scratch)
472
+ s = @map_tys.map do |k_ty, v_ty|
473
+ k = k_ty.screen_name(scratch)
474
+ v = v_ty.screen_name(scratch)
475
+ "#{ k }=>#{ v }"
476
+ end.join(", ")
477
+ "{#{ s }}"
478
+ end
479
+
480
+ def pretty_print(q)
481
+ q.group(9, "Elements[", "]") do
482
+ q.seplist(@map_tys) do |k_ty, v_ty|
483
+ q.group do
484
+ q.pp k_ty
485
+ q.text '=>'
486
+ q.group(1) do
487
+ q.breakable ''
488
+ q.pp v_ty
489
+ end
490
+ end
491
+ end
492
+ end
493
+ end
494
+
495
+ def consistent?(other, subst)
496
+ subst2 = subst.dup
497
+ other.map_tys.each do |k1, v1|
498
+ found = false
499
+ @map_tys.each do |k0, v0|
500
+ subst3 = subst2.dup
501
+ if k0.consistent?(k1, subst3) && v0.consistent?(v1, subst3)
502
+ subst2.replace(subst3)
503
+ found = true
504
+ break
505
+ end
506
+ end
507
+ return false unless found
508
+ end
509
+ subst.replace(subst2)
510
+ true
511
+ end
512
+
513
+ def substitute(subst, depth)
514
+ map_tys = {}
515
+ @map_tys.each do |k_ty_orig, v_ty_orig|
516
+ k_ty = k_ty_orig.substitute(subst, depth)
517
+ v_ty = v_ty_orig.substitute(subst, depth)
518
+ k_ty.each_child_global do |k_ty|
519
+ # This is a temporal hack to mitigate type explosion
520
+ k_ty = Type.any if k_ty.is_a?(Type::Array)
521
+ k_ty = Type.any if k_ty.is_a?(Type::Hash)
522
+ if map_tys[k_ty]
523
+ map_tys[k_ty] = map_tys[k_ty].union(v_ty)
524
+ else
525
+ map_tys[k_ty] = v_ty
526
+ end
527
+ end
528
+ end
529
+ Elements.new(map_tys)
530
+ end
531
+
532
+ def squash
533
+ all_k_ty, all_v_ty = Type.bot, Type.bot
534
+ @map_tys.each do |k_ty, v_ty|
535
+ all_k_ty = all_k_ty.union(k_ty)
536
+ all_v_ty = all_v_ty.union(v_ty)
537
+ end
538
+ return all_k_ty, all_v_ty
539
+ end
540
+
541
+ def [](key_ty)
542
+ val_ty = Type.bot
543
+ @map_tys.each do |k_ty, v_ty|
544
+ if k_ty.consistent?(key_ty, {})
545
+ val_ty = val_ty.union(v_ty)
546
+ end
547
+ end
548
+ val_ty
549
+ end
550
+
551
+ def update(idx, ty)
552
+ map_tys = @map_tys.dup
553
+ idx.each_child_global do |idx|
554
+ # This is a temporal hack to mitigate type explosion
555
+ idx = Type.any if idx.is_a?(Type::Array)
556
+ idx = Type.any if idx.is_a?(Type::Hash)
557
+
558
+ if map_tys[idx]
559
+ map_tys[idx] = map_tys[idx].union(ty)
560
+ else
561
+ map_tys[idx] = ty
562
+ end
563
+ end
564
+ Elements.new(map_tys)
565
+ end
566
+
567
+ def union(other)
568
+ return self if self == other
569
+ raise "Array::Elements merge Hash::Elements" if other.is_a?(Array::Elements)
570
+
571
+ map_tys = @map_tys.dup
572
+ other.map_tys.each do |k_ty, v_ty|
573
+ if map_tys[k_ty]
574
+ map_tys[k_ty] = map_tys[k_ty].union(v_ty)
575
+ else
576
+ map_tys[k_ty] = v_ty
577
+ end
578
+ end
579
+
580
+ Elements.new(map_tys)
581
+ end
582
+ end
583
+ end
584
+
585
+ class LocalHash < Type
586
+ def initialize(id, base_type)
587
+ @id = id
588
+ @base_type = base_type
589
+ end
590
+
591
+ attr_reader :id, :base_type
592
+
593
+ def inspect
594
+ "Type::LocalHash[#{ @id }]"
595
+ end
596
+
597
+ def screen_name(scratch)
598
+ #raise "LocalHash must not be included in signature"
599
+ "LocalHash!"
600
+ end
601
+
602
+ def globalize(env, visited, depth)
603
+ if visited[self] || depth <= 0
604
+ Type.any
605
+ else
606
+ visited[self] = true
607
+ elems = env.get_container_elem_types(@id)
608
+ if elems
609
+ elems = elems.globalize(env, visited, depth - 1)
610
+ else
611
+ elems = Hash::Elements.new({Type.any => Type.any})
612
+ end
613
+ Hash.new(elems, @base_type)
614
+ end
615
+ end
616
+
617
+ def get_method(mid, scratch)
618
+ @base_type.get_method(mid, scratch)
619
+ end
620
+
621
+ def consistent?(other, subst)
622
+ raise "must not be used"
623
+ end
624
+ end
625
+ end
626
+ end