typeprof 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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