typeprof 0.1.2 → 0.4.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 (129) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +3 -0
  3. data/Gemfile +2 -2
  4. data/Gemfile.lock +10 -21
  5. data/LICENSE +21 -0
  6. data/README.md +1 -1
  7. data/doc/demo.md +398 -0
  8. data/doc/doc.ja.md +11 -1
  9. data/doc/doc.md +16 -7
  10. data/exe/typeprof +2 -1
  11. data/lib/typeprof.rb +9 -0
  12. data/lib/typeprof/analyzer.rb +455 -364
  13. data/lib/typeprof/arguments.rb +397 -0
  14. data/lib/typeprof/block.rb +133 -0
  15. data/lib/typeprof/builtin.rb +125 -116
  16. data/lib/typeprof/cli.rb +62 -71
  17. data/lib/typeprof/config.rb +114 -0
  18. data/lib/typeprof/container-type.rb +208 -27
  19. data/lib/typeprof/export.rb +201 -96
  20. data/lib/typeprof/import.rb +451 -365
  21. data/lib/typeprof/iseq.rb +43 -2
  22. data/lib/typeprof/method.rb +139 -100
  23. data/lib/typeprof/type.rb +138 -297
  24. data/lib/typeprof/utils.rb +4 -18
  25. data/lib/typeprof/version.rb +3 -0
  26. data/smoke/arguments2.rb +55 -0
  27. data/smoke/array-each3.rb +1 -4
  28. data/smoke/array12.rb +1 -1
  29. data/smoke/array6.rb +1 -0
  30. data/smoke/block-ambiguous.rb +36 -0
  31. data/smoke/block-args1-rest.rb +62 -0
  32. data/smoke/block-args1.rb +59 -0
  33. data/smoke/block-args2-rest.rb +62 -0
  34. data/smoke/block-args2.rb +59 -0
  35. data/smoke/block-args3-rest.rb +73 -0
  36. data/smoke/block-args3.rb +70 -0
  37. data/smoke/block-blockarg.rb +27 -0
  38. data/smoke/block-kwarg.rb +52 -0
  39. data/smoke/block11.rb +1 -1
  40. data/smoke/block13.rb +9 -0
  41. data/smoke/block13.rbs +3 -0
  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/class.rb +2 -0
  53. data/smoke/constant1.rb +13 -5
  54. data/smoke/constant2.rb +2 -0
  55. data/smoke/cvar.rb +1 -0
  56. data/smoke/demo10.rb +1 -1
  57. data/smoke/demo5.rb +3 -0
  58. data/smoke/demo8.rb +2 -2
  59. data/smoke/demo9.rb +1 -3
  60. data/smoke/flow7.rb +1 -7
  61. data/smoke/flow8.rb +13 -0
  62. data/smoke/gvar.rb +1 -1
  63. data/smoke/gvar2.rb +17 -0
  64. data/smoke/gvar2.rbs +1 -0
  65. data/smoke/hash4.rb +1 -1
  66. data/smoke/inheritance2.rb +6 -0
  67. data/smoke/instance_eval.rb +1 -1
  68. data/smoke/int_times.rb +1 -1
  69. data/smoke/ivar3.rb +16 -0
  70. data/smoke/ivar3.rbs +3 -0
  71. data/smoke/keyword3.rb +1 -2
  72. data/smoke/keyword4.rb +1 -1
  73. data/smoke/manual-rbs2.rb +1 -1
  74. data/smoke/manual-rbs3.rb +12 -0
  75. data/smoke/manual-rbs3.rbs +3 -0
  76. data/smoke/module4.rb +5 -0
  77. data/smoke/multiple-superclass.rb +12 -0
  78. data/smoke/next2.rb +1 -1
  79. data/smoke/optional1.rb +1 -1
  80. data/smoke/optional2.rb +1 -1
  81. data/smoke/optional3.rb +10 -0
  82. data/smoke/proc4.rb +1 -1
  83. data/smoke/rbs-alias.rb +9 -0
  84. data/smoke/rbs-alias.rbs +4 -0
  85. data/smoke/rbs-attr.rb +26 -0
  86. data/smoke/rbs-attr.rbs +5 -0
  87. data/smoke/rbs-extend.rb +9 -0
  88. data/smoke/rbs-extend.rbs +7 -0
  89. data/smoke/rbs-interface.rb +24 -0
  90. data/smoke/rbs-interface.rbs +12 -0
  91. data/smoke/rbs-proc1.rb +9 -0
  92. data/smoke/rbs-proc1.rbs +3 -0
  93. data/smoke/rbs-proc2.rb +20 -0
  94. data/smoke/rbs-proc2.rbs +3 -0
  95. data/smoke/rbs-proc3.rb +13 -0
  96. data/smoke/rbs-proc3.rbs +4 -0
  97. data/smoke/rbs-record.rb +17 -0
  98. data/smoke/rbs-record.rbs +4 -0
  99. data/smoke/rbs-tyvar.rb +18 -0
  100. data/smoke/rbs-tyvar.rbs +5 -0
  101. data/smoke/rbs-tyvar2.rb +20 -0
  102. data/smoke/rbs-tyvar2.rbs +9 -0
  103. data/smoke/rbs-tyvar3.rb +25 -0
  104. data/smoke/rbs-tyvar3.rbs +4 -0
  105. data/smoke/rbs-vars.rb +39 -0
  106. data/smoke/rbs-vars.rbs +7 -0
  107. data/smoke/rest1.rb +1 -1
  108. data/smoke/rest2.rb +1 -1
  109. data/smoke/rest3.rb +1 -1
  110. data/smoke/rest5.rb +1 -1
  111. data/smoke/rest6.rb +1 -1
  112. data/smoke/retry1.rb +1 -1
  113. data/smoke/return.rb +1 -1
  114. data/smoke/singleton_method.rb +3 -0
  115. data/smoke/step.rb +1 -1
  116. data/smoke/struct.rb +6 -2
  117. data/smoke/struct3.rb +14 -0
  118. data/smoke/super1.rb +18 -0
  119. data/smoke/symbol-proc.rb +24 -0
  120. data/smoke/union-recv.rb +6 -0
  121. data/smoke/user-demo.rb +15 -0
  122. data/smoke/wrong-extend.rb +1 -0
  123. data/tools/setup-insns-def.rb +1 -1
  124. data/tools/stackprof-wrapper.rb +1 -1
  125. data/typeprof.gemspec +12 -4
  126. metadata +68 -10
  127. data/.gitmodules +0 -6
  128. data/run.sh +0 -3
  129. data/smoke/variadic1.rb.notyet +0 -5
@@ -0,0 +1,114 @@
1
+ require "rbconfig"
2
+
3
+ module TypeProf
4
+ ConfigData = Struct.new(
5
+ :rb_files,
6
+ :rbs_files,
7
+ :output,
8
+ :gem_rbs_features,
9
+ :verbose,
10
+ :dir_filter,
11
+ :max_iter,
12
+ :max_sec,
13
+ :options,
14
+ keyword_init: true
15
+ )
16
+
17
+ class ConfigData
18
+ def initialize(**opt)
19
+ opt[:output] ||= $stdout
20
+ opt[:gem_rbs_features] ||= []
21
+ opt[:dir_filter] ||= DEFAULT_DIR_FILTER
22
+ opt[:verbose] ||= 0
23
+ opt[:options] ||= {}
24
+ opt[:options] = {
25
+ type_depth_limit: 5,
26
+ pedantic_output: false,
27
+ show_errors: false,
28
+ stackprof: nil,
29
+ }.merge(opt[:options])
30
+ super(**opt)
31
+ end
32
+
33
+ def check_dir_filter(path)
34
+ dir_filter.reverse_each do |cond, dir|
35
+ return cond unless dir
36
+ return cond if path.start_with?(dir)
37
+ end
38
+ end
39
+
40
+ DEFAULT_DIR_FILTER = [
41
+ [:include],
42
+ [:exclude, RbConfig::CONFIG["prefix"]],
43
+ [:exclude, Gem.dir],
44
+ [:exclude, Gem.user_dir],
45
+ ]
46
+ end
47
+
48
+ def self.analyze(config)
49
+ # Deploy the config to the TypeProf::Config (Note: This is thread unsafe)
50
+ if TypeProf.const_defined?(:Config)
51
+ TypeProf.send(:remove_const, :Config)
52
+ end
53
+ TypeProf.const_set(:Config, config)
54
+
55
+ if Config.options[:stackprof]
56
+ require "stackprof"
57
+ out = "typeprof-stackprof-#{ Config.options[:stackprof] }.dump"
58
+ StackProf.start(mode: Config.options[:stackprof], out: out, raw: true)
59
+ end
60
+
61
+ scratch = Scratch.new
62
+ Builtin.setup_initial_global_env(scratch)
63
+
64
+ Config.gem_rbs_features.each do |feature|
65
+ Import.import_library(scratch, feature)
66
+ end
67
+
68
+ prologue_ctx = Context.new(nil, nil, nil)
69
+ prologue_ep = ExecutionPoint.new(prologue_ctx, -1, nil)
70
+ prologue_env = Env.new(StaticEnv.new(:top, Type.nil, false), [], [], Utils::HashWrapper.new({}))
71
+
72
+ Config.rb_files.each do |file|
73
+ if file.respond_to?(:read)
74
+ iseq = ISeq.compile_str(file.read, file.to_s)
75
+ else
76
+ iseq = ISeq.compile(file)
77
+ end
78
+ ep, env = TypeProf.starting_state(iseq)
79
+ scratch.merge_env(ep, env)
80
+ scratch.add_callsite!(ep.ctx, prologue_ep, prologue_env) {|ty, ep| }
81
+ end
82
+
83
+ Config.rbs_files.each do |path|
84
+ Import.import_rbs_file(scratch, path)
85
+ end
86
+
87
+ result = scratch.type_profile
88
+
89
+ if Config.output.respond_to?(:write)
90
+ scratch.report(result, Config.output)
91
+ else
92
+ open(Config.output, "w") do |output|
93
+ scratch.report(result, output)
94
+ end
95
+ end
96
+
97
+ ensure
98
+ if Config.options[:stackprof] && defined?(StackProf)
99
+ StackProf.stop
100
+ StackProf.results
101
+ end
102
+ end
103
+
104
+ def self.starting_state(iseq)
105
+ cref = CRef.new(:bottom, Type::Builtin[:obj], false) # object
106
+ recv = Type::Instance.new(Type::Builtin[:obj])
107
+ ctx = Context.new(iseq, cref, nil)
108
+ ep = ExecutionPoint.new(ctx, 0, nil)
109
+ locals = [Type.nil] * iseq.locals.size
110
+ env = Env.new(StaticEnv.new(recv, Type.nil, false), locals, [], Utils::HashWrapper.new({}))
111
+
112
+ return ep, env
113
+ end
114
+ end
@@ -6,22 +6,197 @@ module TypeProf
6
6
  raise if !val.is_a?(Utils::StructuralEquality) && !val.is_a?(Integer) && !val.is_a?(Symbol)
7
7
  @val = val
8
8
  @parent = parent
9
- @_hash ||= (@val.hash ^ @parent.hash)
10
9
  end
11
10
 
12
11
  attr_reader :val, :parent
13
12
 
14
- def hash
15
- @_hash
16
- end
17
-
18
13
  def add_id(val)
19
14
  AllocationSite.new(val, self)
20
15
  end
21
16
  end
22
17
 
23
18
  class Type # or AbstractValue
24
- # This is a type for global interface, e.g., TypedISeq.
19
+ # Cell, Array, and Hash are types for global interface, e.g., TypedISeq.
20
+ # Do not push such types to local environment, stack, etc.
21
+
22
+ # The most basic container type for default type parameter class
23
+ class Cell < Type
24
+ def initialize(elems, base_type)
25
+ raise if !elems.is_a?(Cell::Elements)
26
+ @elems = elems # Cell::Elements
27
+ raise unless base_type
28
+ @base_type = base_type
29
+ end
30
+
31
+ attr_reader :elems, :base_type
32
+
33
+ def inspect
34
+ "Type::Cell[#{ @elems.inspect }, base_type: #{ @base_type.inspect }]"
35
+ end
36
+
37
+ def screen_name(scratch)
38
+ str = @elems.screen_name(scratch)
39
+ if str.start_with?("*")
40
+ str = @base_type.screen_name(scratch) + str[1..]
41
+ end
42
+ str
43
+ end
44
+
45
+ def localize(env, alloc_site, depth)
46
+ return env, Type.any if depth <= 0
47
+ alloc_site = alloc_site.add_id(:cell)
48
+ env, elems = @elems.localize(env, alloc_site, depth)
49
+ env.deploy_type(LocalCell, alloc_site, elems, @base_type)
50
+ end
51
+
52
+ def limit_size(limit)
53
+ return Type.any if limit <= 0
54
+ Cell.new(@elems.limit_size(limit - 1), @base_type)
55
+ end
56
+
57
+ def get_method(mid, scratch)
58
+ raise
59
+ end
60
+
61
+ def consistent?(other, subst)
62
+ case other
63
+ when Type::Any then true
64
+ when Type::Var then other.add_subst!(self, subst)
65
+ when Type::Union
66
+ other.types.each do |ty2|
67
+ return true if consistent?(ty2, subst)
68
+ end
69
+ return false
70
+ when Type::Cell
71
+ @elems.size == other.elems.size &&
72
+ @base_type.consistent?(other.base_type, subst) &&
73
+ @elems.zip(other.elems).all? {|elem1, elem2| elem1..consistent?(elem2, subst) }
74
+ else
75
+ self == other
76
+ end
77
+ end
78
+
79
+ def substitute(subst, depth)
80
+ return Type.any if depth <= 0
81
+ elems = @elems.substitute(subst, depth)
82
+ Cell.new(elems, @base_type)
83
+ end
84
+
85
+ class Elements
86
+ include Utils::StructuralEquality
87
+
88
+ def initialize(elems)
89
+ @elems = elems
90
+ end
91
+
92
+ attr_reader :elems
93
+
94
+ def to_local_type(id, base_ty)
95
+ Type::LocalCell.new(id, base_ty)
96
+ end
97
+
98
+ def globalize(env, visited, depth)
99
+ Elements.new(@elems.map {|ty| ty.globalize(env, visited, depth) })
100
+ end
101
+
102
+ def localize(env, alloc_site, depth)
103
+ elems = @elems.map.with_index do |ty, i|
104
+ alloc_site2 = alloc_site.add_id(i)
105
+ env, ty = ty.localize(env, alloc_site2, depth)
106
+ ty
107
+ end
108
+ return env, Elements.new(elems)
109
+ end
110
+
111
+ def limit_size(limit)
112
+ Elements.new(@elems.map {|ty| ty.limit_size(limit) })
113
+ end
114
+
115
+ def screen_name(scratch)
116
+ "*[#{ @elems.map {|ty| ty.screen_name(scratch) }.join(", ") }]"
117
+ end
118
+
119
+ def pretty_print(q)
120
+ q.group(9, "Elements[", "]") do
121
+ q.seplist(@elems) do |elem|
122
+ q.pp elem
123
+ end
124
+ end
125
+ end
126
+
127
+ def consistent?(other, subst)
128
+ false if @elems.size != other.elems.size
129
+ @elems.zip(other.elems) do |ty0, ty1|
130
+ return false unless ty0.consistent?(ty1, subst)
131
+ end
132
+ return true
133
+ end
134
+
135
+ def substitute(subst, depth)
136
+ Elements.new(@elems.map {|ty| ty.substitute(subst, depth) })
137
+ end
138
+
139
+ def [](idx)
140
+ @elems[idx]
141
+ end
142
+
143
+ def update(idx, ty)
144
+ Elements.new(Utils.array_update(@elems, idx, @elems[idx].union(ty)))
145
+ end
146
+
147
+ def union(other)
148
+ return self if self == other
149
+ elems = []
150
+ @elems.zip(other.elems) do |ty0, ty1|
151
+ elems << ty0.union(ty1)
152
+ end
153
+ Elements.new(elems)
154
+ end
155
+ end
156
+ end
157
+
158
+ class LocalCell < Type
159
+ def initialize(id, base_type)
160
+ @id = id
161
+ raise unless base_type
162
+ @base_type = base_type
163
+ end
164
+
165
+ attr_reader :id, :base_type
166
+
167
+ def inspect
168
+ "Type::LocalCell[#{ @id }, base_type: #{ @base_type.inspect }]"
169
+ end
170
+
171
+ def screen_name(scratch)
172
+ #raise "LocalArray must not be included in signature"
173
+ "LocalCell!"
174
+ end
175
+
176
+ def globalize(env, visited, depth)
177
+ if visited[self] || depth <= 0
178
+ Type.any
179
+ else
180
+ visited[self] = true
181
+ elems = env.get_container_elem_types(@id)
182
+ if elems
183
+ elems = elems.globalize(env, visited, depth - 1)
184
+ else
185
+ elems = Cell::Elements.new([]) # XXX
186
+ end
187
+ Cell.new(elems, @base_type)
188
+ end
189
+ end
190
+
191
+ def get_method(mid, scratch)
192
+ @base_type.get_method(mid, scratch)
193
+ end
194
+
195
+ def consistent?(other, subst)
196
+ raise "must not be used"
197
+ end
198
+ end
199
+
25
200
  # Do not insert Array type to local environment, stack, etc.
26
201
  class Array < Type
27
202
  def initialize(elems, base_type)
@@ -29,7 +204,6 @@ module TypeProf
29
204
  @elems = elems # Array::Elements
30
205
  raise unless base_type
31
206
  @base_type = base_type
32
- # XXX: need infinite recursion
33
207
  end
34
208
 
35
209
  attr_reader :elems, :base_type
@@ -47,18 +221,11 @@ module TypeProf
47
221
  str
48
222
  end
49
223
 
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
224
  def localize(env, alloc_site, depth)
58
225
  return env, Type.any if depth <= 0
59
226
  alloc_site = alloc_site.add_id(:ary)
60
227
  env, elems = @elems.localize(env, alloc_site, depth - 1)
61
- env.deploy_array_type(alloc_site, elems, @base_type)
228
+ env.deploy_type(LocalArray, alloc_site, elems, @base_type)
62
229
  end
63
230
 
64
231
  def limit_size(limit)
@@ -78,6 +245,7 @@ module TypeProf
78
245
  other.types.each do |ty2|
79
246
  return true if consistent?(ty2, subst)
80
247
  end
248
+ return false
81
249
  when Type::Array
82
250
  @base_type.consistent?(other.base_type, subst) && @elems.consistent?(other.elems, subst)
83
251
  else
@@ -102,8 +270,7 @@ module TypeProf
102
270
 
103
271
  attr_reader :lead_tys, :rest_ty
104
272
 
105
- def to_local_type(id)
106
- base_ty = Type::Instance.new(Type::Builtin[:ary])
273
+ def to_local_type(id, base_ty)
107
274
  Type::LocalArray.new(id, base_ty)
108
275
  end
109
276
 
@@ -171,6 +338,11 @@ module TypeProf
171
338
  @lead_tys.inject(@rest_ty) {|ty1, ty2| ty1.union(ty2) } #.union(Type.nil) # is this needed?
172
339
  end
173
340
 
341
+ def squash_or_any
342
+ ty = squash
343
+ ty == Type.bot ? Type.any : ty
344
+ end
345
+
174
346
  def [](idx)
175
347
  if idx >= 0
176
348
  if idx < @lead_tys.size
@@ -365,18 +537,11 @@ module TypeProf
365
537
  @elems.screen_name(scratch)
366
538
  end
367
539
 
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
540
  def localize(env, alloc_site, depth)
376
541
  return env, Type.any if depth <= 0
377
542
  alloc_site = alloc_site.add_id(:hash)
378
543
  env, elems = @elems.localize(env, alloc_site, depth - 1)
379
- env.deploy_hash_type(alloc_site, elems, @base_type)
544
+ env.deploy_type(LocalHash, alloc_site, elems, @base_type)
380
545
  end
381
546
 
382
547
  def limit_size(limit)
@@ -396,6 +561,7 @@ module TypeProf
396
561
  other.types.each do |ty2|
397
562
  return true if consistent?(ty2, subst)
398
563
  end
564
+ return false
399
565
  when Type::Hash
400
566
  @base_type.consistent?(other.base_type, subst) && @elems.consistent?(other.elems, subst)
401
567
  else
@@ -427,8 +593,7 @@ module TypeProf
427
593
 
428
594
  attr_reader :map_tys
429
595
 
430
- def to_local_type(id)
431
- base_ty = Type::Instance.new(Type::Builtin[:hash])
596
+ def to_local_type(id, base_ty)
432
597
  Type::LocalHash.new(id, base_ty)
433
598
  end
434
599
 
@@ -579,6 +744,22 @@ module TypeProf
579
744
 
580
745
  Elements.new(map_tys)
581
746
  end
747
+
748
+ def to_keywords
749
+ kw_tys = {}
750
+ @map_tys.each do |key_ty, val_ty|
751
+ if key_ty.is_a?(Type::Symbol)
752
+ kw_tys[key_ty.sym] = val_ty
753
+ else
754
+ all_val_ty = Type.bot
755
+ @map_tys.each do |_key_ty, val_ty|
756
+ all_val_ty = all_val_ty.union(val_ty)
757
+ end
758
+ return { nil => all_val_ty }
759
+ end
760
+ end
761
+ kw_tys
762
+ end
582
763
  end
583
764
  end
584
765
 
@@ -25,19 +25,26 @@ module TypeProf
25
25
  ntrace
26
26
  end
27
27
 
28
+ def show_message(terminated, output)
29
+ if terminated
30
+ output.puts "# CAUTION: Type profiling was terminated prematurely because of the limitation"
31
+ output.puts
32
+ end
33
+ end
34
+
28
35
  def show_error(errors, backward_edge, output)
29
36
  return if errors.empty?
30
37
  return unless Config.options[:show_errors]
31
38
 
32
39
  output.puts "# Errors"
33
40
  errors.each do |ep, msg|
34
- if ENV["TYPE_PROFILER_DETAIL"]
41
+ if ENV["TP_DETAIL"]
35
42
  backtrace = filter_backtrace(generate_analysis_trace(ep, {}, backward_edge))
36
43
  else
37
44
  backtrace = [ep]
38
45
  end
39
46
  loc, *backtrace = backtrace.map do |ep|
40
- ep.source_location
47
+ ep&.source_location
41
48
  end
42
49
  output.puts "#{ loc }: #{ msg }"
43
50
  backtrace.each do |loc|
@@ -57,13 +64,15 @@ module TypeProf
57
64
  output.puts
58
65
  end
59
66
 
60
- def show_gvars(scratch, gvar_write, output)
67
+ def show_gvars(scratch, gvars, output)
61
68
  # A signature for global variables is not supported in RBS
62
- return if gvar_write.empty?
69
+ return if gvars.dump.empty?
63
70
 
64
71
  output.puts "# Global variables"
65
- gvar_write.each do |gvar_name, ty|
66
- output.puts "# #{ gvar_name } : #{ ty.screen_name(scratch) }"
72
+ gvars.dump.each do |gvar_name, entry|
73
+ next if entry.type == Type.bot
74
+ s = entry.rbs_declared ? "#" : ""
75
+ output.puts s + "#{ gvar_name } : #{ entry.type.screen_name(scratch) }"
67
76
  end
68
77
  output.puts
69
78
  end
@@ -72,122 +81,148 @@ module TypeProf
72
81
  class RubySignatureExporter
73
82
  def initialize(
74
83
  scratch,
75
- class_defs, iseq_method_to_ctxs, sig_fargs, sig_ret, yields
84
+ class_defs, iseq_method_to_ctxs
76
85
  )
77
86
  @scratch = scratch
78
87
  @class_defs = class_defs
79
88
  @iseq_method_to_ctxs = iseq_method_to_ctxs
80
- @sig_fargs = sig_fargs
81
- @sig_ret = sig_ret
82
- @yields = yields
83
89
  end
84
90
 
85
- def show(stat_eps, output)
86
- output.puts "# Classes" # and Modules
91
+ def conv_class(namespace, class_def, inner_classes)
92
+ @scratch.namespace = namespace
87
93
 
88
- stat_classes = {}
89
- stat_methods = {}
90
- first = true
91
- @class_defs.each_value do |class_def|
92
- included_mods = class_def.modules[false].filter_map do |visible, mod_def|
93
- mod_def.name if visible
94
- end
94
+ if class_def.superclass
95
+ omit = @class_defs[class_def.superclass].klass_obj == Type::Builtin[:obj] || class_def.klass_obj == Type::Builtin[:obj]
96
+ superclass = omit ? nil : @scratch.get_class_name(@class_defs[class_def.superclass].klass_obj)
97
+ end
95
98
 
96
- explicit_methods = {}
97
- iseq_methods = {}
98
- attr_methods = {}
99
- class_def.methods.each do |(singleton, mid), mdefs|
100
- mdefs.each do |mdef|
101
- case mdef
102
- when ISeqMethodDef
103
- ctxs = @iseq_method_to_ctxs[mdef]
104
- next unless ctxs
99
+ @scratch.namespace = class_def.name
105
100
 
106
- ctxs.each do |ctx|
107
- next if mid != ctx.mid
101
+ consts = {}
102
+ class_def.consts.each do |name, (ty, absolute_path)|
103
+ next if ty.is_a?(Type::Class)
104
+ next if !absolute_path || Config.check_dir_filter(absolute_path) == :exclude
105
+ consts[name] = ty.screen_name(@scratch)
106
+ end
108
107
 
109
- method_name = ctx.mid
110
- method_name = "self.#{ method_name }" if singleton
108
+ included_mods = class_def.modules[false].filter_map do |mod_def, absolute_paths|
109
+ next if absolute_paths.all? {|path| !path || Config.check_dir_filter(path) == :exclude }
110
+ Type::Instance.new(mod_def.klass_obj).screen_name(@scratch)
111
+ end
111
112
 
112
- fargs = @sig_fargs[ctx]
113
- ret_tys = @sig_ret[ctx]
113
+ extended_mods = class_def.modules[true].filter_map do |mod_def, absolute_paths|
114
+ next if absolute_paths.all? {|path| !path || Config.check_dir_filter(path) == :exclude }
115
+ Type::Instance.new(mod_def.klass_obj).screen_name(@scratch)
116
+ end
114
117
 
115
- iseq_methods[method_name] ||= []
116
- iseq_methods[method_name] << @scratch.show_signature(fargs, @yields[ctx], ret_tys)
117
- end
118
- when AttrMethodDef
119
- mid = mid.to_s[0..-2].to_sym if mid.to_s.end_with?("=")
120
- method_name = mid
121
- method_name = "self.#{ mid }" if singleton
122
- method_name = [method_name, :"@#{ mid }" != mdef.ivar]
123
- if attr_methods[method_name]
124
- if attr_methods[method_name][0] != mdef.kind
125
- attr_methods[method_name][0] = :accessor
126
- end
127
- else
128
- ty = class_def.ivars.write[[singleton, mdef.ivar]] || Type.any
129
- attr_methods[method_name] = [mdef.kind, ty.screen_name(@scratch)]
130
- end
131
- when TypedMethodDef
132
- if mdef.rbs_source
133
- method_name, sigs = mdef.rbs_source
134
- explicit_methods[method_name] = sigs
118
+ explicit_methods = {}
119
+ iseq_methods = {}
120
+ attr_methods = {}
121
+ ivars = class_def.ivars.dump
122
+ cvars = class_def.cvars.dump
123
+
124
+ class_def.methods.each do |(singleton, mid), mdefs|
125
+ mdefs.each do |mdef|
126
+ case mdef
127
+ when ISeqMethodDef
128
+ ctxs = @iseq_method_to_ctxs[mdef]
129
+ next unless ctxs
130
+
131
+ ctxs.each do |ctx|
132
+ next if mid != ctx.mid
133
+ next if Config.check_dir_filter(ctx.iseq.absolute_path) == :exclude
134
+
135
+ method_name = ctx.mid
136
+ method_name = "self.#{ method_name }" if singleton
137
+
138
+ iseq_methods[method_name] ||= []
139
+ iseq_methods[method_name] << @scratch.show_method_signature(ctx)
140
+ end
141
+ when AttrMethodDef
142
+ next if Config.check_dir_filter(mdef.absolute_path) == :exclude
143
+ mid = mid.to_s[0..-2].to_sym if mid.to_s.end_with?("=")
144
+ method_name = mid
145
+ method_name = "self.#{ mid }" if singleton
146
+ method_name = [method_name, :"@#{ mid }" != mdef.ivar]
147
+ if attr_methods[method_name]
148
+ if attr_methods[method_name][0] != mdef.kind
149
+ attr_methods[method_name][0] = :accessor
135
150
  end
151
+ else
152
+ entry = ivars[[singleton, mdef.ivar]]
153
+ ty = entry ? entry.type : Type.any
154
+ attr_methods[method_name] = [mdef.kind, ty.screen_name(@scratch)]
155
+ end
156
+ when TypedMethodDef
157
+ if mdef.rbs_source
158
+ method_name, sigs = mdef.rbs_source
159
+ explicit_methods[method_name] = sigs
136
160
  end
137
161
  end
138
162
  end
163
+ end
139
164
 
140
- ivars = class_def.ivars.write.map do |(singleton, var), ty|
141
- next unless var.to_s.start_with?("@")
142
- var = "self.#{ var }" if singleton
143
- next if attr_methods[[singleton ? "self.#{ var.to_s[1..] }" : var.to_s[1..].to_sym, false]]
144
- [var, ty.screen_name(@scratch)]
145
- end.compact
165
+ ivars = ivars.map do |(singleton, var), entry|
166
+ next if entry.absolute_paths.all? {|path| Config.check_dir_filter(path) == :exclude }
167
+ ty = entry.type
168
+ next unless var.to_s.start_with?("@")
169
+ var = "self.#{ var }" if singleton
170
+ next if attr_methods[[singleton ? "self.#{ var.to_s[1..] }" : var.to_s[1..].to_sym, false]]
171
+ [var, ty.screen_name(@scratch), entry.rbs_declared]
172
+ end.compact
146
173
 
147
- cvars = class_def.cvars.write.map do |var, ty|
148
- [var, ty.screen_name(@scratch)]
149
- end
174
+ cvars = cvars.map do |var, entry|
175
+ next if entry.absolute_paths.all? {|path| Config.check_dir_filter(path) == :exclude }
176
+ [var, entry.type.screen_name(@scratch), entry.rbs_declared]
177
+ end
150
178
 
151
- next if included_mods.empty? && ivars.empty? && cvars.empty? && iseq_methods.empty? && attr_methods.empty?
179
+ if !class_def.absolute_path || Config.check_dir_filter(class_def.absolute_path) == :exclude
180
+ return nil if consts.empty? && included_mods.empty? && extended_mods.empty? && ivars.empty? && cvars.empty? && iseq_methods.empty? && attr_methods.empty? && inner_classes.empty?
181
+ end
152
182
 
153
- output.puts unless first
154
- first = false
183
+ @scratch.namespace = nil
155
184
 
156
- if class_def.superclass
157
- object = @class_defs[class_def.superclass].klass_obj == Type::Builtin[:obj]
158
- superclass = object ? "" : " < #{ @class_defs[class_def.superclass].name }"
159
- end
185
+ ClassData.new(
186
+ kind: class_def.kind,
187
+ name: class_def.name,
188
+ superclass: superclass,
189
+ consts: consts,
190
+ included_mods: included_mods,
191
+ extended_mods: extended_mods,
192
+ ivars: ivars,
193
+ cvars: cvars,
194
+ attr_methods: attr_methods,
195
+ explicit_methods: explicit_methods,
196
+ iseq_methods: iseq_methods,
197
+ inner_classes: inner_classes,
198
+ )
199
+ end
160
200
 
161
- output.puts "#{ class_def.kind } #{ class_def.name }#{ superclass }"
162
- included_mods.sort.each do |ty|
163
- output.puts " include #{ ty }"
164
- end
165
- ivars.each do |var, ty|
166
- output.puts " #{ var } : #{ ty }" unless var.start_with?("_")
167
- end
168
- cvars.each do |var, ty|
169
- output.puts " #{ var } : #{ ty }"
170
- end
171
- attr_methods.each do |(method_name, hidden), (kind, ty)|
172
- output.puts " attr_#{ kind } #{ method_name }#{ hidden ? "()" : "" } : #{ ty }"
173
- end
174
- explicit_methods.each do |method_name, sigs|
175
- sigs = sigs.sort.join("\n" + "#" + " " * (method_name.size + 6) + "| ")
176
- output.puts "# def #{ method_name } : #{ sigs }"
177
- end
178
- iseq_methods.each do |method_name, sigs|
179
- sigs = sigs.sort.join("\n" + " " * (method_name.size + 7) + "| ")
180
- output.puts " def #{ method_name } : #{ sigs }"
201
+ ClassData = Struct.new(:kind, :name, :superclass, :consts, :included_mods, :extended_mods, :ivars, :cvars, :attr_methods, :explicit_methods, :iseq_methods, :inner_classes, keyword_init: true)
202
+
203
+ def show(stat_eps, output)
204
+ # make the class hierarchy
205
+ root = {}
206
+ @class_defs.each_value do |class_def|
207
+ h = root
208
+ class_def.name.each do |name|
209
+ h = h[name] ||= {}
181
210
  end
182
- output.puts "end"
211
+ h[:class_def] = class_def
183
212
  end
184
213
 
214
+ hierarchy = build_class_hierarchy([], root)
215
+
216
+ output.puts "# Classes" # and Modules
217
+
218
+ show_class_hierarchy(0, hierarchy, output, true)
219
+
185
220
  if ENV["TP_STAT"]
186
- output.puts "statistics:"
187
- output.puts " %d execution points" % stat_eps.size
188
- output.puts " %d classes" % stat_classes.size
189
- output.puts " %d methods (in total)" % stat_methods.size
221
+ output.puts ""
222
+ output.puts "# TypeProf statistics:"
223
+ output.puts "# %d execution points" % stat_eps.size
190
224
  end
225
+
191
226
  if ENV["TP_COVERAGE"]
192
227
  coverage = {}
193
228
  stat_eps.each do |ep|
@@ -196,8 +231,78 @@ module TypeProf
196
231
  (coverage[path] ||= [])[lineno] ||= 0
197
232
  (coverage[path] ||= [])[lineno] += 1
198
233
  end
199
- File.binwrite("coverage.dump", Marshal.dump(coverage))
234
+ File.binwrite("typeprof-analysis-coverage.dump", Marshal.dump(coverage))
235
+ end
236
+ end
237
+
238
+ def build_class_hierarchy(namespace, hierarchy)
239
+ hierarchy.map do |name, h|
240
+ class_def = h.delete(:class_def)
241
+ class_data = conv_class(namespace, class_def, build_class_hierarchy(namespace + [name], h))
242
+ class_data
243
+ end.compact
244
+ end
245
+
246
+ def show_class_hierarchy(depth, hierarchy, output, first)
247
+ hierarchy.each do |class_data|
248
+ output.puts unless first
249
+ first = false
250
+
251
+ show_class_data(depth, class_data, output)
252
+ end
253
+ end
254
+
255
+ def show_const(namespace, path)
256
+ return path.last.to_s if namespace == path
257
+ i = 0
258
+ i += 1 while namespace[i] && namespace[i] == path[i]
259
+ path[i..].join("::")
260
+ end
261
+
262
+ def show_class_data(depth, class_data, output)
263
+ indent = " " * depth
264
+ name = class_data.name.last
265
+ superclass = " < " + class_data.superclass if class_data.superclass
266
+ output.puts indent + "#{ class_data.kind } #{ name }#{ superclass }"
267
+ first = true
268
+ class_data.consts.each do |name, ty|
269
+ output.puts indent + " #{ name } : #{ ty }"
270
+ first = false
271
+ end
272
+ class_data.included_mods.sort.each do |mod|
273
+ output.puts indent + " include #{ mod }"
274
+ first = false
275
+ end
276
+ class_data.extended_mods.sort.each do |mod|
277
+ output.puts indent + " extend #{ mod }"
278
+ first = false
279
+ end
280
+ class_data.ivars.each do |var, ty, rbs_declared|
281
+ s = rbs_declared ? "# " : " "
282
+ output.puts indent + s + "#{ var } : #{ ty }" unless var.start_with?("_")
283
+ first = false
284
+ end
285
+ class_data.cvars.each do |var, ty, rbs_declared|
286
+ s = rbs_declared ? "# " : " "
287
+ output.puts indent + s + "#{ var } : #{ ty }"
288
+ first = false
289
+ end
290
+ class_data.attr_methods.each do |(method_name, hidden), (kind, ty)|
291
+ output.puts indent + " attr_#{ kind } #{ method_name }#{ hidden ? "()" : "" } : #{ ty }"
292
+ first = false
293
+ end
294
+ class_data.explicit_methods.each do |method_name, sigs|
295
+ sigs = sigs.sort.join("\n" + indent + "#" + " " * (method_name.size + 6) + "| ")
296
+ output.puts indent + "# def #{ method_name } : #{ sigs }"
297
+ first = false
298
+ end
299
+ class_data.iseq_methods.each do |method_name, sigs|
300
+ sigs = sigs.sort.join("\n" + indent + " " * (method_name.size + 7) + "| ")
301
+ output.puts indent + " def #{ method_name } : #{ sigs }"
302
+ first = false
200
303
  end
304
+ show_class_hierarchy(depth + 1, class_data.inner_classes, output, first)
305
+ output.puts indent + "end"
201
306
  end
202
307
  end
203
308
  end