typeprof 0.15.0 → 0.15.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: dcefd3a4da176820246ad53757286aa1f7f8b2d21a6d1300763c91ba252e95ca
4
- data.tar.gz: 1bf7429a4cf7d59e1ab4ca10f5f065a22b85adbbda922c67060d0ede64aab60f
3
+ metadata.gz: 52c9e6c0c338fb19da0264929bce36b903cdedf7be3b6a031deef525e28be8d4
4
+ data.tar.gz: 8c17fe8549a7c44c8a11a1db9a40384b05f4025e5ec23140fee09eaf349d855c
5
5
  SHA512:
6
- metadata.gz: ff14f14aa923ca807db9d7f1bc7f537c46e735bd194a0b59758dfdba0e04c04175ca0505fe6c00f739fec7933eeeab5010646891f56e14d5245f5b8613dd7d3b
7
- data.tar.gz: 73a05bece353a7b4dbefba9f16e723fb088795e9f029b143721b03543e53ae19a8adb9908b3de41c839fbfb10a8d34c76ed0fa4f1b072801d7334233be63b1c9
6
+ metadata.gz: 74d8eddbd6010287d33c9891a59112f7f9edf1aa2f3ea24d07142e1d81e1c03508b1362a3630f092138352616a8258e973784d926293882894ba933123851f88
7
+ data.tar.gz: 77bc3c7f268b11663f561829c4b1a2c4917e137c0e56c3b4e734f29862c011e7095b2d1939a352534f78003159d90f07f8de81ce1304819f4f79e5d5ac6f7242
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- typeprof (0.15.0)
4
+ typeprof (0.15.1)
5
5
  rbs (>= 1.3.1)
6
6
 
7
7
  GEM
@@ -11,7 +11,7 @@ GEM
11
11
  docile (1.4.0)
12
12
  power_assert (2.0.0)
13
13
  rake (13.0.1)
14
- rbs (1.3.1)
14
+ rbs (1.3.3)
15
15
  simplecov (0.21.2)
16
16
  docile (~> 1.1)
17
17
  simplecov-html (~> 0.11)
@@ -286,6 +286,7 @@ module TypeProf
286
286
  @anonymous_struct_gen_id = 0
287
287
 
288
288
  @types_being_shown = []
289
+ @namespace = nil
289
290
  end
290
291
 
291
292
  def add_entrypoint(iseq)
@@ -1118,15 +1119,16 @@ module TypeProf
1118
1119
  env = @ep2env[ep]
1119
1120
  raise "nil env" unless env
1120
1121
 
1121
- insn, operands = ep.ctx.iseq.insns[ep.pc]
1122
+ insn = ep.ctx.iseq.insns[ep.pc]
1123
+ operands = insn.operands
1122
1124
 
1123
1125
  if Config.verbose >= 2
1124
1126
  # XXX: more dedicated output
1125
1127
  puts "DEBUG: stack=%p" % [env.stack]
1126
- puts "DEBUG: %s (%s) PC=%d insn=%s sp=%d" % [ep.source_location, ep.ctx.iseq.name, ep.pc, insn, env.stack.size]
1128
+ puts "DEBUG: %s (%s) PC=%d insn=%s sp=%d" % [ep.source_location, ep.ctx.iseq.name, ep.pc, insn.insn, env.stack.size]
1127
1129
  end
1128
1130
 
1129
- case insn
1131
+ case insn.insn
1130
1132
  when :_iseq_body_start
1131
1133
  # XXX: reconstruct and record the method signature
1132
1134
  iseq = ep.ctx.iseq
@@ -1641,7 +1643,7 @@ module TypeProf
1641
1643
  return
1642
1644
  end
1643
1645
 
1644
- when :getlocal, :getblockparam, :getblockparamproxy
1646
+ when :getlocal
1645
1647
  var_idx, scope_idx, _escaped = operands
1646
1648
  if scope_idx == 0
1647
1649
  ty = env.get_local(-var_idx+2)
@@ -1682,18 +1684,12 @@ module TypeProf
1682
1684
  getlocal_operands, _dup_operands, branch_operands = operands
1683
1685
  var_idx, _scope_idx, _escaped = getlocal_operands
1684
1686
  ret_ty = env.get_local(-var_idx+2)
1685
- unless ret_ty
1686
- p env.locals
1687
- raise
1688
- end
1689
1687
 
1690
1688
  branchtype, target, = branch_operands
1691
1689
  # branchtype: :if or :unless or :nil
1692
1690
  ep_then = ep.next
1693
1691
  ep_else = ep.jump(target)
1694
1692
 
1695
- var_idx, _scope_idx, _escaped = getlocal_operands
1696
-
1697
1693
  ret_ty.each_child do |ret_ty|
1698
1694
  flow_env = env.local_update(-var_idx+2, ret_ty).push(ret_ty)
1699
1695
  case ret_ty
@@ -1707,6 +1703,31 @@ module TypeProf
1707
1703
  end
1708
1704
  end
1709
1705
  return
1706
+ when :dup_setlocal_branch
1707
+ _dup_operands, setlocal_operands, branch_operands = operands
1708
+
1709
+ env, (ret_ty,) = env.pop(1)
1710
+
1711
+ var_idx, _scope_idx, _escaped = setlocal_operands
1712
+
1713
+ branchtype, target, = branch_operands
1714
+ # branchtype: :if or :unless or :nil
1715
+ ep_then = ep.next
1716
+ ep_else = ep.jump(target)
1717
+
1718
+ ret_ty.each_child do |ret_ty|
1719
+ flow_env = env.local_update(-var_idx+2, ret_ty)
1720
+ case ret_ty
1721
+ when Type.any
1722
+ merge_env(ep_then, flow_env)
1723
+ merge_env(ep_else, flow_env)
1724
+ when Type::Instance.new(Type::Builtin[:false]), Type.nil
1725
+ merge_env(branchtype == :if ? ep_then : ep_else, flow_env)
1726
+ else
1727
+ merge_env(branchtype == :if ? ep_else : ep_then, flow_env)
1728
+ end
1729
+ end
1730
+ return
1710
1731
  when :getlocal_checkmatch_branch
1711
1732
  getlocal_operands, branch_operands = operands
1712
1733
  var_idx, _scope_idx, _escaped = getlocal_operands
@@ -1970,7 +1991,7 @@ module TypeProf
1970
1991
  end
1971
1992
  env = env.push(ty)
1972
1993
  else
1973
- raise "Unknown insn: #{ insn }"
1994
+ raise "Unknown insn: #{ insn.insn }"
1974
1995
  end
1975
1996
 
1976
1997
  add_edge(ep, ep)
@@ -2204,7 +2225,7 @@ module TypeProf
2204
2225
  if blk.is_a?(Type::Proc)
2205
2226
  blk.block_body.do_call(aargs, ep, env, self, replace_recv_ty: replace_recv_ty, replace_cref: replace_cref, &ctn)
2206
2227
  else
2207
- warn(ep, "non-proc is passed as a block")
2228
+ warn(ep, "non-proc is passed as a block") if blk != Type.any
2208
2229
  ctn[Type.any, ep, env]
2209
2230
  end
2210
2231
  end
@@ -813,6 +813,7 @@ module TypeProf
813
813
  Type::Builtin[:exc] = scratch.get_constant(klass_obj, :Exception)
814
814
  Type::Builtin[:encoding] = scratch.get_constant(klass_obj, :Encoding)
815
815
  Type::Builtin[:enumerator] = scratch.get_constant(klass_obj, :Enumerator)
816
+ Type::Builtin[:kernel] = scratch.get_constant(klass_obj, :Kernel)
816
817
 
817
818
  klass_vmcore = Type::Builtin[:vmcore]
818
819
  klass_ary = Type::Builtin[:ary]
@@ -825,6 +826,7 @@ module TypeProf
825
826
  scratch.set_custom_method(klass_vmcore, :"core#undef_method", Builtin.method(:vmcore_undef_method))
826
827
  scratch.set_custom_method(klass_vmcore, :"core#hash_merge_kwd", Builtin.method(:vmcore_hash_merge_kwd))
827
828
  scratch.set_custom_method(klass_vmcore, :"core#raise", Builtin.method(:vmcore_raise))
829
+
828
830
  scratch.set_custom_method(klass_vmcore, :lambda, Builtin.method(:lambda))
829
831
  scratch.set_singleton_custom_method(klass_obj, :"new", Builtin.method(:object_s_new))
830
832
  scratch.set_custom_method(klass_obj, :p, Builtin.method(:kernel_p), false)
@@ -880,6 +882,12 @@ module TypeProf
880
882
  str_ty = Type::Instance.new(Type::Builtin[:str])
881
883
  env_ty = Type.gen_hash {|h| h[str_ty] = Type.optional(str_ty) }
882
884
  scratch.add_constant(klass_obj, :ENV, env_ty, false)
885
+
886
+ scratch.search_method(Type::Builtin[:kernel], false, :sprintf) do |mdefs,|
887
+ mdefs.each do |mdef|
888
+ scratch.add_method(klass_vmcore, :"core#sprintf", false, mdef)
889
+ end
890
+ end
883
891
  end
884
892
  end
885
893
  end
data/lib/typeprof/iseq.rb CHANGED
@@ -3,75 +3,65 @@ module TypeProf
3
3
  # https://github.com/ruby/ruby/pull/4468
4
4
  CASE_WHEN_CHECKMATCH = RubyVM::InstructionSequence.compile("case 1; when Integer; end").to_a.last.any? {|insn,| insn == :checkmatch }
5
5
 
6
- include Utils::StructuralEquality
7
-
8
- def self.compile(file)
9
- opt = RubyVM::InstructionSequence.compile_option
10
- opt[:inline_const_cache] = false
11
- opt[:peephole_optimization] = false
12
- opt[:specialized_instruction] = false
13
- opt[:operands_unification] = false
14
- opt[:coverage_enabled] = false
15
- new(RubyVM::InstructionSequence.compile_file(file, **opt).to_a)
6
+ class << self
7
+ def compile(file)
8
+ compile_core(nil, file)
9
+ end
10
+
11
+ def compile_str(str, path = nil)
12
+ compile_core(str, path)
13
+ end
14
+
15
+ private def compile_core(str, path)
16
+ opt = RubyVM::InstructionSequence.compile_option
17
+ opt[:inline_const_cache] = false
18
+ opt[:peephole_optimization] = false
19
+ opt[:specialized_instruction] = false
20
+ opt[:operands_unification] = false
21
+ opt[:coverage_enabled] = false
22
+
23
+ if str
24
+ iseq = RubyVM::InstructionSequence.compile(str, path, **opt)
25
+ else
26
+ iseq = RubyVM::InstructionSequence.compile_file(path, **opt)
27
+ end
28
+
29
+ return new(iseq.to_a)
30
+ end
16
31
  end
17
32
 
18
- def self.compile_str(str, path = nil)
19
- opt = RubyVM::InstructionSequence.compile_option
20
- opt[:inline_const_cache] = false
21
- opt[:peephole_optimization] = false
22
- opt[:specialized_instruction] = false
23
- opt[:operands_unification] = false
24
- opt[:coverage_enabled] = false
25
- new(RubyVM::InstructionSequence.compile(str, path, **opt).to_a)
33
+ Insn = Struct.new(:insn, :operands, :lineno)
34
+ class Insn
35
+ def check?(insn_cmp, operands_cmp = nil)
36
+ return insn == insn_cmp && (!operands_cmp || operands == operands_cmp)
37
+ end
26
38
  end
27
39
 
28
- FRESH_ID = [0]
40
+ ISEQ_FRESH_ID = [0]
29
41
 
30
42
  def initialize(iseq)
31
- @id = FRESH_ID[0]
32
- FRESH_ID[0] += 1
43
+ @id = (ISEQ_FRESH_ID[0] += 1)
33
44
 
34
45
  _magic, _major_version, _minor_version, _format_type, _misc,
35
46
  @name, @path, @absolute_path, @start_lineno, @type,
36
47
  @locals, @fargs_format, catch_table, insns = *iseq
37
48
 
38
- case @type
39
- when :method, :block
40
- if @fargs_format[:opt]
41
- label = @fargs_format[:opt].last
42
- i = insns.index(label) + 1
43
- else
44
- i = insns.find_index {|insn| insn.is_a?(Array) }
45
- end
46
- # skip keyword initialization
47
- while insns[i][0] == :checkkeyword
48
- raise if insns[i + 1][0] != :branchif
49
- label = insns[i + 1][1]
50
- i = insns.index(label) + 1
51
- end
52
- insns[i, 0] = [[:_iseq_body_start]]
53
- end
49
+ convert_insns(insns)
54
50
 
55
- # rescue/ensure clauses need to have a dedicated return addresses
56
- # because they requires to be virtually called.
57
- # So, this preprocess adds "nop" to make a new insn for their return addresses
58
- special_labels = {}
59
- catch_table.map do |type, iseq, first, last, cont, stack_depth|
60
- special_labels[cont] = true if type == :rescue || type == :ensure
61
- end
51
+ add_body_start_marker(insns)
62
52
 
63
- @insns = []
64
- @linenos = []
53
+ add_exception_cont_marker(insns, catch_table)
65
54
 
66
- labels = setup_iseq(insns, special_labels)
55
+ labels = create_label_table(insns)
67
56
 
68
- # checkmatch->branch
69
- # send->branch
57
+ @insns = setup_insns(insns, labels)
58
+
59
+ @fargs_format[:opt] = @fargs_format[:opt].map {|l| labels[l] } if @fargs_format[:opt]
70
60
 
71
61
  @catch_table = []
72
62
  catch_table.map do |type, iseq, first, last, cont, stack_depth|
73
63
  iseq = iseq ? ISeq.new(iseq) : nil
74
- target = labels[special_labels[cont] ? :"#{ cont }_special" : cont]
64
+ target = labels[cont]
75
65
  entry = [type, iseq, target, stack_depth]
76
66
  labels[first].upto(labels[last]) do |i|
77
67
  @catch_table[i] ||= []
@@ -79,43 +69,143 @@ module TypeProf
79
69
  end
80
70
  end
81
71
 
82
- merge_branches
72
+ rename_insn_types
73
+
74
+ unify_instructions
75
+ end
76
+
77
+ def source_location(pc)
78
+ "#{ @path }:#{ @insns[pc].lineno }"
79
+ end
83
80
 
84
- analyze_stack
81
+ attr_reader :name, :path, :absolute_path, :start_lineno, :type, :locals, :fargs_format, :catch_table, :insns
82
+ attr_reader :id
83
+
84
+ def pretty_print(q)
85
+ q.text "ISeq["
86
+ q.group do
87
+ q.nest(1) do
88
+ q.breakable ""
89
+ q.text "@type= #{ @type }"
90
+ q.breakable ", "
91
+ q.text "@name= #{ @name }"
92
+ q.breakable ", "
93
+ q.text "@path= #{ @path }"
94
+ q.breakable ", "
95
+ q.text "@absolute_path= #{ @absolute_path }"
96
+ q.breakable ", "
97
+ q.text "@start_lineno= #{ @start_lineno }"
98
+ q.breakable ", "
99
+ q.text "@fargs_format= #{ @fargs_format.inspect }"
100
+ q.breakable ", "
101
+ q.text "@insns="
102
+ q.group(2) do
103
+ @insns.each_with_index do |(insn, *operands), i|
104
+ q.breakable
105
+ q.group(2, "#{ i }: #{ insn.to_s }", "") do
106
+ q.pp operands
107
+ end
108
+ end
109
+ end
110
+ end
111
+ q.breakable
112
+ end
113
+ q.text "]"
85
114
  end
86
115
 
87
116
  def <=>(other)
88
117
  @id <=> other.id
89
118
  end
90
119
 
91
- def setup_iseq(insns, special_labels)
120
+ # Remove lineno entry and convert instructions to Insn instances
121
+ def convert_insns(insns)
122
+ ninsns = []
123
+ lineno = 0
124
+ insns.each do |e|
125
+ case e
126
+ when Integer # lineno
127
+ lineno = e
128
+ when Symbol # label or trace
129
+ ninsns << e
130
+ when Array
131
+ insn, *operands = e
132
+ ninsns << Insn.new(insn, operands, lineno)
133
+ else
134
+ raise "unknown iseq entry: #{ e }"
135
+ end
136
+ end
137
+ insns.replace(ninsns)
138
+ end
139
+
140
+ # Insert a dummy instruction "_iseq_body_start"
141
+ def add_body_start_marker(insns)
142
+ case @type
143
+ when :method, :block
144
+ # skip initialization code of optional arguments
145
+ if @fargs_format[:opt]
146
+ label = @fargs_format[:opt].last
147
+ i = insns.index(label) + 1
148
+ else
149
+ i = insns.find_index {|insn| insn.is_a?(Insn) }
150
+ end
151
+
152
+ # skip initialization code of keyword arguments
153
+ while insns[i][0] == :checkkeyword
154
+ raise if insns[i + 1].insn != :branchif
155
+ label = insns[i + 1].operands[0]
156
+ i = insns.index(label) + 1
157
+ end
158
+
159
+ insns.insert(i, Insn.new(:_iseq_body_start, [], @start_lineno))
160
+ end
161
+ end
162
+
163
+ # Insert "nop" instruction to continuation point of exception handlers
164
+ def add_exception_cont_marker(insns, catch_table)
165
+ # rescue/ensure clauses need to have a dedicated return addresses
166
+ # because they requires to be virtually called.
167
+ # So, this preprocess adds "nop" to make a new insn for their return addresses
168
+ exception_cont_labels = {}
169
+ catch_table.map! do |type, iseq, first, last, cont, stack_depth|
170
+ if type == :rescue || type == :ensure
171
+ exception_cont_labels[cont] = true
172
+ cont = :"#{ cont }_exception_cont"
173
+ end
174
+ [type, iseq, first, last, cont, stack_depth]
175
+ end
176
+
92
177
  i = 0
178
+ while i < insns.size
179
+ e = insns[i]
180
+ if exception_cont_labels[e]
181
+ insns.insert(i, :"#{ e }_exception_cont", Insn.new(:nop, []))
182
+ i += 2
183
+ end
184
+ i += 1
185
+ end
186
+ end
187
+
188
+ def create_label_table(insns)
189
+ pc = 0
93
190
  labels = {}
94
- ninsns = []
95
191
  insns.each do |e|
96
- if e.is_a?(Symbol) && e.to_s.start_with?("label")
97
- if special_labels[e]
98
- labels[:"#{ e }_special"] = i
99
- ninsns << [:nop]
100
- i += 1
101
- end
102
- labels[e] = i
192
+ if e.is_a?(Symbol)
193
+ labels[e] = pc
103
194
  else
104
- i += 1 if e.is_a?(Array)
105
- ninsns << e
195
+ pc += 1
106
196
  end
107
197
  end
198
+ labels
199
+ end
108
200
 
109
- lineno = 0
110
- ninsns.each do |e|
201
+ def setup_insns(insns, labels)
202
+ ninsns = []
203
+ insns.each do |e|
111
204
  case e
112
- when Integer # lineno
113
- lineno = e
114
205
  when Symbol # label or trace
115
206
  nil
116
- when Array
117
- insn, *operands = e
118
- operands = (INSN_TABLE[insn] || []).zip(operands).map do |type, operand|
207
+ when Insn
208
+ operands = (INSN_TABLE[e.insn] || []).zip(e.operands).map do |type, operand|
119
209
  case type
120
210
  when "ISEQ"
121
211
  operand && ISeq.new(operand)
@@ -131,81 +221,64 @@ module TypeProf
131
221
  end
132
222
  end
133
223
 
134
- @insns << [insn, operands]
135
- @linenos << lineno
224
+ ninsns << Insn.new(e.insn, operands, e.lineno)
136
225
  else
137
226
  raise "unknown iseq entry: #{ e }"
138
227
  end
139
228
  end
140
-
141
- @fargs_format[:opt] = @fargs_format[:opt].map {|l| labels[l] } if @fargs_format[:opt]
142
-
143
- labels
229
+ ninsns
144
230
  end
145
231
 
146
- def merge_branches
147
- @insns.size.times do |i|
148
- insn, operands = @insns[i]
149
- case insn
232
+ def rename_insn_types
233
+ @insns.each do |insn|
234
+ case insn.insn
150
235
  when :branchif
151
- @insns[i] = [:branch, [:if] + operands]
236
+ insn.insn, insn.operands = :branch, [:if] + insn.operands
152
237
  when :branchunless
153
- @insns[i] = [:branch, [:unless] + operands]
238
+ insn.insn, insn.operands = :branch, [:unless] + insn.operands
154
239
  when :branchnil
155
- @insns[i] = [:branch, [:nil] + operands]
240
+ insn.insn, insn.operands = :branch, [:nil] + insn.operands
241
+ when :getblockparam, :getblockparamproxy
242
+ insn.insn = :getlocal
156
243
  end
157
244
  end
158
245
  end
159
246
 
160
- def source_location(pc)
161
- "#{ @path }:#{ @linenos[pc] }"
162
- end
163
-
164
- attr_reader :name, :path, :absolute_path, :start_lineno, :type, :locals, :fargs_format, :catch_table, :insns, :linenos
165
- attr_reader :id
166
-
167
- def pretty_print(q)
168
- q.text "ISeq["
169
- q.group do
170
- q.nest(1) do
171
- q.breakable ""
172
- q.text "@type= #{ @type }"
173
- q.breakable ", "
174
- q.text "@name= #{ @name }"
175
- q.breakable ", "
176
- q.text "@path= #{ @path }"
177
- q.breakable ", "
178
- q.text "@absolute_path= #{ @absolute_path }"
179
- q.breakable ", "
180
- q.text "@start_lineno= #{ @start_lineno }"
181
- q.breakable ", "
182
- q.text "@fargs_format= #{ @fargs_format.inspect }"
183
- q.breakable ", "
184
- q.text "@insns="
185
- q.group(2) do
186
- @insns.each_with_index do |(insn, *operands), i|
187
- q.breakable
188
- q.group(2, "#{ i }: #{ insn.to_s }", "") do
189
- q.pp operands
190
- end
191
- end
192
- end
193
- end
194
- q.breakable
195
- end
196
- q.text "]"
197
- end
247
+ # Unify some instructions for flow-sensitive analysis
248
+ def unify_instructions
249
+ # This method rewrites instructions to enable flow-sensitive analysis.
250
+ #
251
+ # Consider `if x; ...; else; ... end`.
252
+ # When the variable `x` is of type "Integer | nil",
253
+ # we want to make sure that `x` is "Integer" in then clause.
254
+ # So, we need to split the environment to two ones:
255
+ # one is that `x` is of type "Integer", and the other is that
256
+ # `x` is type "nil".
257
+ #
258
+ # However, `if x` is compiled to "getlocal; branch".
259
+ # TypeProf evaluates them as follows:
260
+ #
261
+ # * "getlocal" pushes the value of `x` to the stack, amd
262
+ # * "branch" checks the value on the top of the stack
263
+ #
264
+ # TypeProf does not keep where the value comes from, so
265
+ # it is difficult to split the environment when evaluating "branch".
266
+ #
267
+ # This method rewrites "getlocal; branch" to "nop; getlocal_branch".
268
+ # The two instructions are unified to "getlocal_branch" instruction,
269
+ # so TypeProf can split the environment.
270
+ #
271
+ # This is a very fragile appoach because it highly depends on the compiler of Ruby.
198
272
 
199
- def analyze_stack
200
273
  # gather branch targets
201
274
  # TODO: catch_table should be also considered
202
275
  branch_targets = {}
203
- @insns.each do |insn, operands|
204
- case insn
276
+ @insns.each do |insn|
277
+ case insn.insn
205
278
  when :branch
206
- branch_targets[operands[1]] = true
279
+ branch_targets[insn.operands[1]] = true
207
280
  when :jump
208
- branch_targets[operands[0]] = true
281
+ branch_targets[insn.operands[0]] = true
209
282
  end
210
283
  end
211
284
 
@@ -215,25 +288,27 @@ module TypeProf
215
288
  case_branch_list = []
216
289
  if CASE_WHEN_CHECKMATCH
217
290
  (@insns.size - 1).times do |i|
218
- insn0, getlocal_operands = @insns[i]
219
- next unless [:getlocal, :getblockparam, :getblockparamproxy].include?(insn0) && getlocal_operands[1] == 0
291
+ insn = @insns[i]
292
+ next unless insn.insn == :getlocal && insn.operands[1] == 0
293
+ getlocal_operands = insn.operands
220
294
  nops = [i]
221
295
  new_insns = []
222
296
  j = i + 1
223
297
  while true
224
- case @insns[j]
225
- when [:dup, []]
226
- break unless @insns[j + 1] == [:putnil, []]
227
- break unless @insns[j + 2] == [:putobject, [true]]
228
- break unless @insns[j + 3][0] == :getconstant # TODO: support A::B::C
229
- break unless @insns[j + 4] == [:checkmatch, [2]]
230
- break unless @insns[j + 5][0] == :branch
231
- target_pc = @insns[j + 5][1][1]
232
- break unless @insns[target_pc] == [:pop, []]
298
+ case @insns[j].insn
299
+ when :dup
300
+ break unless @insns[j + 1].check?(:putnil, [])
301
+ break unless @insns[j + 2].check?(:putobject, [true])
302
+ break unless @insns[j + 3].check?(:getconstant) # TODO: support A::B::C
303
+ break unless @insns[j + 4].check?(:checkmatch, [2])
304
+ break unless @insns[j + 5].check?(:branch)
305
+ target_pc = @insns[j + 5].operands[1]
306
+ break unless @insns[target_pc].check?(:pop, [])
233
307
  nops << j << (j + 4) << target_pc
234
- new_insns << [j + 5, [:getlocal_checkmatch_branch, [getlocal_operands, @insns[j + 5][1]]]]
308
+ branch_operands = @insns[j + 5][1]
309
+ new_insns << [j + 5, Insn.new(:getlocal_checkmatch_branch, [getlocal_operands, branch_operands])]
235
310
  j += 6
236
- when [:pop, []]
311
+ when :pop
237
312
  nops << j
238
313
  case_branch_list << [nops, new_insns]
239
314
  break
@@ -244,25 +319,28 @@ module TypeProf
244
319
  end
245
320
  else
246
321
  (@insns.size - 1).times do |i|
247
- insn0, getlocal_operands = @insns[i]
248
- next unless [:getlocal, :getblockparam, :getblockparamproxy].include?(insn0) && getlocal_operands[1] == 0
322
+ insn = @insns[i]
323
+ next unless insn.insn == :getlocal && insn.operands[1] == 0
324
+ getlocal_operands = insn.operands
249
325
  nops = []
250
326
  new_insns = []
251
327
  j = i + 1
252
328
  while true
253
- case @insns[j]
254
- when [:putnil, []]
255
- break unless @insns[j + 1] == [:putobject, [true]]
256
- break unless @insns[j + 2][0] == :getconstant # TODO: support A::B::C
257
- break unless @insns[j + 3] == [:topn, [1]]
258
- break unless @insns[j + 4] == [:send, [{:mid=>:===, :flag=>20, :orig_argc=>1}, nil]]
259
- break unless @insns[j + 5][0] == :branch
260
- target_pc = @insns[j + 5][1][1]
261
- break unless @insns[target_pc] == [:pop, []]
329
+ insn = @insns[j]
330
+ if insn.check?(:putnil, [])
331
+ break unless @insns[j + 1].check?(:putobject, [true])
332
+ break unless @insns[j + 2].check?(:getconstant) # TODO: support A::B::C
333
+ break unless @insns[j + 3].check?(:topn, [1])
334
+ break unless @insns[j + 4].check?(:send, [{:mid=>:===, :flag=>20, :orig_argc=>1}, nil])
335
+ break unless @insns[j + 5].check?(:branch)
336
+ target_pc = @insns[j + 5].operands[1]
337
+ break unless @insns[target_pc].check?(:pop, [])
262
338
  nops << (j + 4) #<< target_pc
263
- new_insns << [j + 5, [:arg_getlocal_send_branch, [getlocal_operands, @insns[j + 4][1], @insns[j + 5][1]]]]
339
+ send_operands = @insns[j + 4][1]
340
+ branch_operands = @insns[j + 5][1]
341
+ new_insns << [j + 5, Insn.new(:arg_getlocal_send_branch, [getlocal_operands, send_operands, branch_operands])]
264
342
  j += 6
265
- when [:pop, []]
343
+ elsif insn.check?(:pop, [])
266
344
  #nops << j
267
345
  case_branch_list << [nops, new_insns]
268
346
  break
@@ -273,15 +351,15 @@ module TypeProf
273
351
  end
274
352
  end
275
353
  case_branch_list.each do |nops, new_insns|
276
- nops.each {|i| @insns[i] = [:nop, []] }
354
+ nops.each {|i| @insns[i] = Insn.new(:nop, []) }
277
355
  new_insns.each {|i, insn| @insns[i] = insn }
278
356
  end
279
357
 
280
358
  # find a pattern: getlocal(recv), ..., send (is_a?, respond_to?), branch
281
359
  recv_getlocal_send_branch_list = []
282
360
  (@insns.size - 1).times do |i|
283
- insn, operands = @insns[i]
284
- if insn == :getlocal && operands[1] == 0
361
+ insn = @insns[i]
362
+ if insn.insn == :getlocal && insn.operands[1] == 0
285
363
  j = i + 1
286
364
  sp = 1
287
365
  while @insns[j]
@@ -297,94 +375,118 @@ module TypeProf
297
375
  end
298
376
  recv_getlocal_send_branch_list.each do |i, j|
299
377
  next if (i + 1 .. j + 1).any? {|i| branch_targets[i] }
300
- _insn, getlocal_operands = @insns[i]
301
- _insn, send_operands = @insns[j]
302
- _insn, branch_operands = @insns[j + 1]
303
- @insns[j] = [:nop]
304
- @insns[j + 1] = [:recv_getlocal_send_branch, [getlocal_operands, send_operands, branch_operands]]
378
+ getlocal_operands = @insns[i].operands
379
+ send_operands = @insns[j].operands
380
+ branch_operands = @insns[j + 1].operands
381
+ @insns[j] = Insn.new(:nop, [])
382
+ @insns[j + 1] = Insn.new(:recv_getlocal_send_branch, [getlocal_operands, send_operands, branch_operands])
305
383
  end
306
384
 
307
385
  # find a pattern: getlocal, send (===), branch
308
386
  arg_getlocal_send_branch_list = []
309
387
  (@insns.size - 1).times do |i|
310
- insn1, operands1 = @insns[i]
311
- next unless insn1 == :getlocal && operands1[1] == 0
312
- insn2, operands2 = @insns[i + 1]
313
- next unless insn2 == :send
314
- send_opt = operands2[0]
315
- next unless send_opt[:flag] == 16 && send_opt[:orig_argc] == 1
316
- insn3, _operands3 = @insns[i + 2]
317
- next unless insn3 == :branch
388
+ insn1 = @insns[i]
389
+ next unless insn1.insn == :getlocal && insn1.operands[1] == 0
390
+ insn2 = @insns[i + 1]
391
+ next unless insn2.insn == :send
392
+ send_operands = insn2.operands[0]
393
+ next unless send_operands[:flag] == 16 && send_operands[:orig_argc] == 1
394
+ insn3 = @insns[i + 2]
395
+ next unless insn3.insn == :branch
318
396
  arg_getlocal_send_branch_list << i
319
397
  end
320
398
  arg_getlocal_send_branch_list.each do |i|
321
399
  next if (i .. i + 2).any? {|i| branch_targets[i] }
322
- _insn, getlocal_operands = @insns[i]
323
- _insn, send_operands = @insns[i + 1]
324
- _insn, branch_operands = @insns[i + 2]
325
- @insns[i + 1] = [:nop]
326
- @insns[i + 2] = [:arg_getlocal_send_branch, [getlocal_operands, send_operands, branch_operands]]
400
+ getlocal_operands = @insns[i].operands
401
+ send_operands = @insns[i + 1].operands
402
+ branch_operands = @insns[i + 2].operands
403
+ @insns[i + 1] = Insn.new(:nop, [])
404
+ @insns[i + 2] = Insn.new(:arg_getlocal_send_branch, [getlocal_operands, send_operands, branch_operands])
327
405
  end
328
406
 
329
407
  # find a pattern: send (block_given?), branch
330
408
  send_branch_list = []
331
409
  (@insns.size - 1).times do |i|
332
- insn, _operands = @insns[i]
333
- if insn == :send
334
- insn, _operands = @insns[i + 1]
335
- if insn == :branch
410
+ insn = @insns[i]
411
+ if insn.insn == :send
412
+ insn = @insns[i + 1]
413
+ if insn.insn == :branch
336
414
  send_branch_list << i
337
415
  end
338
416
  end
339
417
  end
340
418
  send_branch_list.each do |i|
341
419
  next if branch_targets[i + 1]
342
- _insn, send_operands = @insns[i]
343
- _insn, branch_operands = @insns[i + 1]
344
- @insns[i] = [:nop]
345
- @insns[i + 1] = [:send_branch, [send_operands, branch_operands]]
420
+ send_operands = @insns[i].operands
421
+ branch_operands = @insns[i + 1].operands
422
+ @insns[i] = Insn.new(:nop, [])
423
+ @insns[i + 1] = Insn.new(:send_branch, [send_operands, branch_operands])
346
424
  end
347
425
 
348
426
  # find a pattern: getlocal, dup, branch
349
427
  (@insns.size - 2).times do |i|
350
428
  next if branch_targets[i + 1] || branch_targets[i + 2]
351
- insn0, getlocal_operands = @insns[i]
352
- insn1, dup_operands = @insns[i + 1]
353
- insn2, branch_operands = @insns[i + 2]
354
- if insn0 == :getlocal && insn1 == :dup && insn2 == :branch && getlocal_operands[1] == 0
355
- @insns[i ] = [:nop]
356
- @insns[i + 1] = [:nop]
357
- @insns[i + 2] = [:getlocal_dup_branch, [getlocal_operands, dup_operands, branch_operands]]
429
+ insn0 = @insns[i]
430
+ insn1 = @insns[i + 1]
431
+ insn2 = @insns[i + 2]
432
+ if insn0.insn == :getlocal && insn1.insn == :dup && insn2.insn == :branch && insn0.operands[1] == 0
433
+ getlocal_operands = insn0.operands
434
+ dup_operands = insn1.operands
435
+ branch_operands = insn2.operands
436
+ @insns[i ] = Insn.new(:nop, [])
437
+ @insns[i + 1] = Insn.new(:nop, [])
438
+ @insns[i + 2] = Insn.new(:getlocal_dup_branch, [getlocal_operands, dup_operands, branch_operands])
439
+ end
440
+ end
441
+
442
+ # find a pattern: dup, setlocal, branch
443
+ (@insns.size - 2).times do |i|
444
+ next if branch_targets[i + 1] || branch_targets[i + 2]
445
+ insn0 = @insns[i]
446
+ insn1 = @insns[i + 1]
447
+ insn2 = @insns[i + 2]
448
+ if insn0.insn == :dup && insn1.insn == :setlocal && insn2.insn == :branch && insn1.operands[1] == 0
449
+ dup_operands = insn0.operands
450
+ setlocal_operands = insn1.operands
451
+ branch_operands = insn2.operands
452
+ @insns[i ] = Insn.new(:nop, [])
453
+ @insns[i + 1] = Insn.new(:nop, [])
454
+ @insns[i + 2] = Insn.new(:dup_setlocal_branch, [dup_operands, setlocal_operands, branch_operands])
358
455
  end
359
456
  end
360
457
 
361
458
  # find a pattern: dup, branch
362
459
  (@insns.size - 1).times do |i|
363
460
  next if branch_targets[i + 1]
364
- insn0, dup_operands = @insns[i]
365
- insn1, branch_operands = @insns[i + 1]
366
- if insn0 == :dup && insn1 == :branch
367
- @insns[i ] = [:nop]
368
- @insns[i + 1] = [:dup_branch, [dup_operands, branch_operands]]
461
+ insn0 = @insns[i]
462
+ insn1 = @insns[i + 1]
463
+ if insn0.insn == :dup && insn1.insn == :branch
464
+ dup_operands = insn0.operands
465
+ branch_operands = insn1.operands
466
+ @insns[i ] = Insn.new(:nop, [])
467
+ @insns[i + 1] = Insn.new(:dup_branch, [dup_operands, branch_operands])
369
468
  end
370
469
  end
371
470
 
372
471
  # find a pattern: getlocal, branch
373
472
  (@insns.size - 1).times do |i|
374
473
  next if branch_targets[i + 1]
375
- insn0, getlocal_operands = @insns[i]
376
- insn1, branch_operands = @insns[i + 1]
377
- if [:getlocal, :getblockparam, :getblockparamproxy].include?(insn0) && getlocal_operands[1] == 0 && insn1 == :branch
378
- @insns[i ] = [:nop]
379
- @insns[i + 1] = [:getlocal_branch, [getlocal_operands, branch_operands]]
474
+ insn0 = @insns[i]
475
+ insn1 = @insns[i + 1]
476
+ if insn0.insn == :getlocal && insn0.operands[1] == 0 && insn1.insn == :branch
477
+ getlocal_operands = insn0.operands
478
+ branch_operands = insn1.operands
479
+ @insns[i ] = Insn.new(:nop, [])
480
+ @insns[i + 1] = Insn.new(:getlocal_branch, [getlocal_operands, branch_operands])
380
481
  end
381
482
  end
382
483
  end
383
484
 
384
485
  def check_send_branch(sp, j)
385
- insn, operands = @insns[j]
486
+ insn = @insns[j]
487
+ operands = insn.operands
386
488
 
387
- case insn
489
+ case insn.insn
388
490
  when :putspecialobject, :putnil, :putobject, :duparray, :putstring,
389
491
  :putself
390
492
  sp += 1
@@ -422,7 +524,7 @@ module TypeProf
422
524
  argc += 1 # receiver
423
525
  argc += kw_arg.size if kw_arg
424
526
  sp -= argc
425
- return :match if insn == :send && sp == 0 && @insns[j + 1][0] == :branch
527
+ return :match if insn.insn == :send && sp == 0 && @insns[j + 1].insn == :branch
426
528
  sp += 1
427
529
  when :arg_getlocal_send_branch
428
530
  return # not implemented
@@ -283,12 +283,12 @@ module TypeProf
283
283
  # XXX: a block is passed to a method that does not accept block.
284
284
  # Should we call the passed block with any arguments?
285
285
  ret_ty = ret_ty.remove_type_vars
286
- ctn[ret_ty, caller_ep, ncaller_env]
286
+ ctn[ret_ty, caller_ep, ncaller_env] if ret_ty != Type.bot
287
287
  end
288
288
  else
289
289
  ret_ty = ret_ty.substitute(subst, Config.options[:type_depth_limit])
290
290
  ret_ty = ret_ty.remove_type_vars
291
- ctn[ret_ty, caller_ep, ncaller_env]
291
+ ctn[ret_ty, caller_ep, ncaller_env] if ret_ty != Type.bot
292
292
  end
293
293
  end
294
294
 
data/lib/typeprof/type.rb CHANGED
@@ -895,7 +895,7 @@ module TypeProf
895
895
  fargs << ("**" + all_val_ty.screen_name(scratch))
896
896
  end
897
897
  if Config.options[:show_parameter_names]
898
- farg_names = farg_names.map {|name| name == :type ? :type_ : name } # XXX: workaround of RBS parser bug
898
+ farg_names = farg_names.map {|name| RBS::Parser::KEYWORDS.key?(name.to_s) ? "`#{name}`" : name }
899
899
  farg_names = farg_names.map {|name| name.is_a?(Integer) ? "noname_#{ name }" : name }
900
900
  fargs = fargs.zip(farg_names).map {|farg, name| name ? "#{ farg } #{ name }" : farg }
901
901
  end
@@ -1,3 +1,3 @@
1
1
  module TypeProf
2
- VERSION = "0.15.0"
2
+ VERSION = "0.15.1"
3
3
  end
data/smoke/break2.rb CHANGED
@@ -12,5 +12,5 @@ __END__
12
12
  # Classes
13
13
  class Object
14
14
  private
15
- def foo: -> (Integer | String)
15
+ def foo: -> String
16
16
  end
@@ -0,0 +1,17 @@
1
+ def type(type)
2
+ end
3
+
4
+ def out(*out)
5
+ end
6
+
7
+ def untyped(untyped:)
8
+ end
9
+
10
+ __END__
11
+ # Classes
12
+ class Object
13
+ private
14
+ def type: (untyped `type`) -> nil
15
+ def out: (*untyped `out`) -> nil
16
+ def untyped: (untyped: untyped) -> nil
17
+ end
data/smoke/next2.rb CHANGED
@@ -13,5 +13,5 @@ __END__
13
13
  # Classes
14
14
  class Object
15
15
  private
16
- def foo: { -> (Integer | String) } -> (Integer | String)
16
+ def foo: { -> Integer } -> Integer
17
17
  end
data/smoke/or_raise.rb ADDED
@@ -0,0 +1,18 @@
1
+ def foo
2
+ if rand < 0.5
3
+ 1
4
+ end
5
+ end
6
+
7
+ def bar
8
+ x = foo or raise("nil")
9
+ x.times { }
10
+ end
11
+
12
+ __END__
13
+ # Classes
14
+ class Object
15
+ private
16
+ def foo: -> Integer?
17
+ def bar: -> Integer
18
+ end
@@ -1,4 +1,5 @@
1
1
  # RUBY_VERSION >= 3.0
2
+ # NO_SHOW_ERRORS
2
3
 
3
4
  def foo
4
5
  case [:a, :b, :c]
@@ -11,12 +12,6 @@ end
11
12
  foo
12
13
 
13
14
  __END__
14
- # Errors
15
- smoke/pattern-match1.rb:5: [error] undefined method: nil#length
16
- smoke/pattern-match1.rb:5: [error] undefined method: nil#[]
17
- smoke/pattern-match1.rb:5: [error] undefined method: nil#[]
18
- smoke/pattern-match1.rb:5: [error] undefined method: nil#[]
19
-
20
15
  # Classes
21
16
  class Object
22
17
  private
data/testbed/ao.rb CHANGED
@@ -291,7 +291,7 @@ end
291
291
 
292
292
  top
293
293
 
294
- if _dummy = false
294
+ if $0 == __FILE__
295
295
  v = Vec.new(0.0, 0.0, 0.0)
296
296
  v.vadd(v)
297
297
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: typeprof
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.15.0
4
+ version: 0.15.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yusuke Endoh
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-07-21 00:00:00.000000000 Z
11
+ date: 2021-08-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rbs
@@ -217,6 +217,7 @@ files:
217
217
  - smoke/hash4.rb
218
218
  - smoke/hash5.rb
219
219
  - smoke/huge_union.rb
220
+ - smoke/identifier_keywords.rb
220
221
  - smoke/included.rb
221
222
  - smoke/inheritance.rb
222
223
  - smoke/inheritance2.rb
@@ -276,6 +277,7 @@ files:
276
277
  - smoke/optional1.rb
277
278
  - smoke/optional2.rb
278
279
  - smoke/optional3.rb
280
+ - smoke/or_raise.rb
279
281
  - smoke/parameterizedd-self.rb
280
282
  - smoke/parameterizedd-self2.rb
281
283
  - smoke/pathname1.rb