typeprof 0.6.1 → 0.7.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.
@@ -7,60 +7,59 @@ module TypeProf
7
7
  def parse(argv)
8
8
  opt = OptionParser.new
9
9
 
10
+ opt.banner = "Usage: #{ opt.program_name } [options] files..."
11
+
10
12
  output = nil
11
13
 
12
14
  # Verbose level:
13
- # * 0: no output
14
- # * 1: show indicator
15
- # * 2: debug print
15
+ # * 0: none
16
+ # * 1: default level
17
+ # * 2: debugging level
16
18
  verbose = 1
17
19
 
18
20
  options = {}
19
21
  dir_filter = nil
20
22
  gem_rbs_features = []
21
- version = false
23
+ show_version = false
22
24
  max_sec = max_iter = nil
23
25
 
24
- opt.on("-o OUTFILE") {|v| output = v }
25
- opt.on("-q", "--quiet") { verbose = 0 }
26
- opt.on("-v", "--verbose") { options[:show_errors] = true }
27
- opt.on("--version") { version = true }
28
- opt.on("-d", "--debug") { verbose = 2 }
29
- opt.on("-I DIR") {|v| $LOAD_PATH << v }
30
- opt.on("-r FEATURE") {|v| gem_rbs_features << v }
31
- opt.on("--max-second SECOND", Float) {|v| max_sec = v }
32
- opt.on("--max-iteration TIMES", Integer) {|v| max_iter = v }
26
+ opt.separator ""
27
+ opt.separator "Options:"
28
+ opt.on("-o OUTFILE", "Output to OUTFILE instead of stdout") {|v| output = v }
29
+ opt.on("-q", "--quiet", "Do not display progress indicator") { options[:show_indicator] = false }
30
+ opt.on("-v", "--verbose", "Alias to --show-errors") { options[:show_errors] = true }
31
+ opt.on("--version", "Display typeprof version") { show_version = true }
32
+ opt.on("-I DIR", "Add DIR to the load/require path") {|v| $LOAD_PATH << v }
33
+ opt.on("-r FEATURE", "Require RBS of the FEATURE gem") {|v| gem_rbs_features << v }
33
34
 
34
- opt.on("--include-dir DIR") do |dir|
35
+ opt.separator ""
36
+ opt.separator "Analysis output options:"
37
+ opt.on("--include-dir DIR", "Include the analysis result of .rb file in DIR") do |dir|
35
38
  # When `--include-dir` option is specified as the first directory option,
36
39
  # typeprof will exclude any files by default unless a file path matches the explicit option
37
40
  dir_filter ||= [[:exclude]]
38
41
  dir_filter << [:include, File.expand_path(dir)]
39
42
  end
40
- opt.on("--exclude-dir DIR") do |dir|
43
+ opt.on("--exclude-dir DIR", "Exclude the analysis result of .rb file in DIR") do |dir|
41
44
  # When `--exclude-dir` option is specified as the first directory option,
42
45
  # typeprof will include any files by default, except Ruby's install directory and Gem directories
43
46
  dir_filter ||= ConfigData::DEFAULT_DIR_FILTER
44
47
  dir_filter << [:exclude, File.expand_path(dir)]
45
48
  end
49
+ opt.on("--[no-]show-errors", "Display possible errors found during the analysis") {|v| options[:show_errors] = v }
50
+ opt.on("--[no-]show-untyped", "Display \"Foo | untyped\" instead of \"Foo\"") {|v| options[:show_untyped] = v }
46
51
 
47
- opt.on("-f OPTION") do |v|
48
- key, args = v.split("=", 2)
49
- case key
50
- when "type-depth-limit"
51
- options[:type_depth_limit] = Integer(args)
52
- when "pedantic-output"
53
- options[:pedantic_output] = true
54
- when "show-errors"
55
- options[:show_errors] = true
56
- when "show-container-raw-elements"
57
- options[:show_container_raw_elements] = true
58
- when "stackprof"
59
- options[:stackprof] = args ? args.to_sym : :cpu
60
- else
61
- raise OptionParser::InvalidOption.new("unknown option: #{ key }")
62
- end
63
- end
52
+ opt.separator ""
53
+ opt.separator "Analysis limit options:"
54
+ opt.on("--max-second SECOND", Float, "Limit the maxium time of analysis (in second)") {|v| max_sec = v }
55
+ opt.on("--max-iteration TIMES", Integer, "Limit the maxium instruction count of analysis") {|v| max_iter = v }
56
+
57
+ opt.separator ""
58
+ opt.separator "Advanced options:"
59
+ opt.on("--[no-]stub-execution", "Force to call all unreachable methods with \"untyped\" arguments") {|v| options[:stub_execution] = v }
60
+ opt.on("--type-depth-limit DEPTH", Integer, "Limit the maximum depth of nested types") {|v| options[:type_depth_limit] = v }
61
+ opt.on("--debug", "Display analysis log (for debugging purpose)") { verbose = 2 }
62
+ opt.on("--[no-]stackprof MODE", /\Acpu|wall|object\z/, "Enable stackprof (for debugging purpose)") {|v| options[:stackprof] = v.to_sym }
64
63
 
65
64
  opt.parse!(argv)
66
65
 
@@ -75,9 +74,9 @@ module TypeProf
75
74
  end
76
75
  end
77
76
 
78
- puts "typeprof #{ VERSION }" if version
77
+ puts "typeprof #{ VERSION }" if show_version
79
78
  if rb_files.empty?
80
- exit if version
79
+ exit if show_version
81
80
  raise OptionParser::InvalidOption.new("no input files")
82
81
  end
83
82
 
@@ -22,9 +22,11 @@ module TypeProf
22
22
  opt[:verbose] ||= 0
23
23
  opt[:options] ||= {}
24
24
  opt[:options] = {
25
- type_depth_limit: 5,
26
- pedantic_output: false,
25
+ show_indicator: true,
26
+ show_untyped: false,
27
27
  show_errors: false,
28
+ stub_execution: true,
29
+ type_depth_limit: 5,
28
30
  stackprof: nil,
29
31
  }.merge(opt[:options])
30
32
  super(**opt)
@@ -77,7 +77,9 @@ module TypeProf
77
77
  return env, Type.any if depth <= 0
78
78
  alloc_site = alloc_site.add_id(:cell).add_id(@base_type)
79
79
  env, elems = @elems.localize(env, alloc_site, depth)
80
- env.deploy_type(LocalCell, alloc_site, elems, @base_type)
80
+ ty = Local.new(Cell, alloc_site, @base_type)
81
+ env = env.deploy_type(alloc_site, elems)
82
+ return env, ty
81
83
  end
82
84
 
83
85
  def limit_size(limit)
@@ -95,6 +97,19 @@ module TypeProf
95
97
  Cell.new(elems, @base_type)
96
98
  end
97
99
 
100
+ def generate_substitution
101
+ subst = {}
102
+ tyvars = @base_type.klass.type_params.map {|name,| Type::Var.new(name) }
103
+ tyvars.zip(@elems.elems) do |tyvar, elem|
104
+ if subst[tyvar]
105
+ subst[tyvar] = subst[tyvar].union(elem)
106
+ else
107
+ subst[tyvar] = elem
108
+ end
109
+ end
110
+ subst
111
+ end
112
+
98
113
  class Elements # Cell
99
114
  include Utils::StructuralEquality
100
115
 
@@ -102,10 +117,14 @@ module TypeProf
102
117
  @elems = elems
103
118
  end
104
119
 
120
+ def self.dummy_elements
121
+ Elements.new([]) # XXX
122
+ end
123
+
105
124
  attr_reader :elems
106
125
 
107
126
  def to_local_type(id, base_ty)
108
- Type::LocalCell.new(id, base_ty)
127
+ Type::Local.new(Cell, id, base_ty)
109
128
  end
110
129
 
111
130
  def globalize(env, visited, depth)
@@ -180,45 +199,6 @@ module TypeProf
180
199
  end
181
200
  end
182
201
 
183
- class LocalCell < ContainerType
184
- def initialize(id, base_type)
185
- @id = id
186
- raise unless base_type
187
- @base_type = base_type
188
- end
189
-
190
- attr_reader :id, :base_type
191
-
192
- def inspect
193
- "Type::LocalCell[#{ @id }, base_type: #{ @base_type.inspect }]"
194
- end
195
-
196
- def screen_name(scratch)
197
- #raise "LocalArray must not be included in signature"
198
- "LocalCell!"
199
- end
200
-
201
- def globalize(env, visited, depth)
202
- if visited[self] || depth <= 0
203
- Type.any
204
- else
205
- visited[self] = true
206
- elems = env.get_container_elem_types(@id)
207
- if elems
208
- elems = elems.globalize(env, visited, depth - 1)
209
- else
210
- elems = Cell::Elements.new([]) # XXX
211
- end
212
- visited.delete(self)
213
- Cell.new(elems, @base_type)
214
- end
215
- end
216
-
217
- def method_dispatch_info
218
- @base_type.method_dispatch_info
219
- end
220
- end
221
-
222
202
  # Do not insert Array type to local environment, stack, etc.
223
203
  class Array < ContainerType
224
204
  def initialize(elems, base_type)
@@ -228,6 +208,10 @@ module TypeProf
228
208
  @base_type = base_type
229
209
  end
230
210
 
211
+ def self.dummy_elements
212
+ Elements.new([], Type.any)
213
+ end
214
+
231
215
  attr_reader :elems, :base_type
232
216
 
233
217
  def inspect
@@ -247,7 +231,9 @@ module TypeProf
247
231
  return env, Type.any if depth <= 0
248
232
  alloc_site = alloc_site.add_id(:ary).add_id(@base_type)
249
233
  env, elems = @elems.localize(env, alloc_site, depth - 1)
250
- env.deploy_type(LocalArray, alloc_site, elems, @base_type)
234
+ ty = Local.new(Array, alloc_site, @base_type)
235
+ env = env.deploy_type(alloc_site, elems)
236
+ return env, ty
251
237
  end
252
238
 
253
239
  def limit_size(limit)
@@ -265,6 +251,10 @@ module TypeProf
265
251
  Array.new(elems, @base_type)
266
252
  end
267
253
 
254
+ def generate_substitution
255
+ { Type::Var.new(:Elem) => @elems.squash }
256
+ end
257
+
268
258
  class Elements # Array
269
259
  include Utils::StructuralEquality
270
260
 
@@ -277,7 +267,7 @@ module TypeProf
277
267
  attr_reader :lead_tys, :rest_ty
278
268
 
279
269
  def to_local_type(id, base_ty)
280
- Type::LocalArray.new(id, base_ty)
270
+ Type::Local.new(Array, id, base_ty)
281
271
  end
282
272
 
283
273
  def globalize(env, visited, depth)
@@ -305,7 +295,7 @@ module TypeProf
305
295
  end
306
296
 
307
297
  def screen_name(scratch)
308
- if Config.options[:show_container_raw_elements] || @rest_ty == Type.bot
298
+ if @rest_ty == Type.bot
309
299
  if @lead_tys.empty?
310
300
  return "Array[bot]" # RBS does not allow an empty tuple "[]"
311
301
  end
@@ -363,7 +353,57 @@ module TypeProf
363
353
  end
364
354
 
365
355
  def [](idx)
366
- if idx >= 0
356
+ if idx.is_a?(Range)
357
+ if @rest_ty == Type.bot
358
+ lead_tys = @lead_tys[idx]
359
+ if lead_tys
360
+ rest_ty = Type.bot
361
+ else
362
+ return Type.nil
363
+ end
364
+ else
365
+ b, e = idx.begin, idx.end
366
+ b = 0 if !b
367
+ if !e
368
+ lead_tys = @lead_tys[idx] || []
369
+ rest_ty = @rest_ty
370
+ elsif b >= 0
371
+ if e >= 0
372
+ if b <= e
373
+ if e < @lead_tys.size
374
+ lead_tys = @lead_tys[idx]
375
+ rest_ty = Type.bot
376
+ else
377
+ lead_tys = @lead_tys[idx] || []
378
+ rest_ty = @rest_ty
379
+ end
380
+ else
381
+ return Type.nil
382
+ end
383
+ else
384
+ lead_tys = @lead_tys[idx] || []
385
+ e = idx.exclude_end? ? e : e == -1 ? @lead_tys.size : e + 1
386
+ rest_ty = (@lead_tys[e + 1..] || []).inject(@rest_ty) {|ty0, ty1| ty0.union(ty1) }
387
+ end
388
+ else
389
+ lead_tys = []
390
+ if e >= 0
391
+ rest_ty = e < @lead_tys.size ? Type.bot : @rest_ty
392
+ range = [0, @lead_tys.size + b].max .. (idx.exclude_end? ? e - 1 : e)
393
+ rest_ty = @lead_tys[range].inject(rest_ty) {|ty0, ty1| ty0.union(ty1) }
394
+ else
395
+ if b <= e
396
+ range = [0, @lead_tys.size + b].max .. (idx.exclude_end? ? e - 1 : e)
397
+ rest_ty = @lead_tys[range].inject(@rest_ty) {|ty0, ty1| ty0.union(ty1) }
398
+ else
399
+ return Type.nil
400
+ end
401
+ end
402
+ end
403
+ end
404
+ base_ty = Type::Instance.new(Type::Builtin[:ary])
405
+ Array.new(Elements.new(lead_tys, rest_ty), base_ty)
406
+ elsif idx >= 0
367
407
  if idx < @lead_tys.size
368
408
  @lead_tys[idx]
369
409
  elsif @rest_ty == Type.bot
@@ -494,48 +534,6 @@ module TypeProf
494
534
  end
495
535
  end
496
536
 
497
- # Do not insert Array type to local environment, stack, etc.
498
- class LocalArray < ContainerType
499
- def initialize(id, base_type)
500
- @id = id
501
- raise unless base_type
502
- @base_type = base_type
503
- end
504
-
505
- attr_reader :id, :base_type
506
-
507
- def inspect
508
- "Type::LocalArray[#{ @id }, base_type: #{ @base_type.inspect }]"
509
- end
510
-
511
- def screen_name(scratch)
512
- #raise "LocalArray must not be included in signature"
513
- "LocalArray!"
514
- end
515
-
516
- def globalize(env, visited, depth)
517
- if visited[self] || depth <= 0
518
- Type.any
519
- else
520
- visited[self] = true
521
- elems = env.get_container_elem_types(@id)
522
- if elems
523
- elems = elems.globalize(env, visited, depth - 1)
524
- else
525
- # TODO: currently out-of-scope array cannot be accessed
526
- elems = Array::Elements.new([], Type.any)
527
- end
528
- visited.delete(self)
529
- Array.new(elems, @base_type)
530
- end
531
- end
532
-
533
- def method_dispatch_info
534
- @base_type.method_dispatch_info
535
- end
536
- end
537
-
538
-
539
537
  class Hash < ContainerType
540
538
  def initialize(elems, base_type)
541
539
  @elems = elems
@@ -557,7 +555,9 @@ module TypeProf
557
555
  return env, Type.any if depth <= 0
558
556
  alloc_site = alloc_site.add_id(:hash).add_id(@base_type)
559
557
  env, elems = @elems.localize(env, alloc_site, depth - 1)
560
- env.deploy_type(LocalHash, alloc_site, elems, @base_type)
558
+ ty = Local.new(Hash, alloc_site, @base_type)
559
+ env = env.deploy_type(alloc_site, elems)
560
+ return env, ty
561
561
  end
562
562
 
563
563
  def limit_size(limit)
@@ -575,6 +575,14 @@ module TypeProf
575
575
  Hash.new(elems, @base_type)
576
576
  end
577
577
 
578
+ def generate_substitution
579
+ tyvar_k = Type::Var.new(:K)
580
+ tyvar_v = Type::Var.new(:V)
581
+ k_ty0, v_ty0 = @elems.squash
582
+ # XXX: need to heuristically replace ret type Hash[K, V] with self, instead of conversative type?
583
+ { tyvar_k => k_ty0, tyvar_v => v_ty0 }
584
+ end
585
+
578
586
  class Elements # Hash
579
587
  include Utils::StructuralEquality
580
588
 
@@ -583,18 +591,21 @@ module TypeProf
583
591
  raise unless k_ty.is_a?(Type)
584
592
  raise unless v_ty.is_a?(Type)
585
593
  raise if k_ty.is_a?(Type::Union)
586
- raise if k_ty.is_a?(Type::LocalArray)
587
- raise if k_ty.is_a?(Type::LocalHash)
594
+ raise if k_ty.is_a?(Type::Local)
588
595
  raise if k_ty.is_a?(Type::Array)
589
596
  raise if k_ty.is_a?(Type::Hash)
590
597
  end
591
598
  @map_tys = map_tys
592
599
  end
593
600
 
601
+ def self.dummy_elements
602
+ Elements.new({Type.any => Type.any})
603
+ end
604
+
594
605
  attr_reader :map_tys
595
606
 
596
607
  def to_local_type(id, base_ty)
597
- Type::LocalHash.new(id, base_ty)
608
+ Type::Local.new(Hash, id, base_ty)
598
609
  end
599
610
 
600
611
  def globalize(env, visited, depth)
@@ -782,21 +793,23 @@ module TypeProf
782
793
  end
783
794
  end
784
795
 
785
- class LocalHash < ContainerType
786
- def initialize(id, base_type)
796
+ class Local < ContainerType
797
+ def initialize(kind, id, base_type)
798
+ @kind = kind
787
799
  @id = id
800
+ raise unless base_type
788
801
  @base_type = base_type
789
802
  end
790
803
 
791
- attr_reader :id, :base_type
804
+ attr_reader :kind, :id, :base_type
792
805
 
793
806
  def inspect
794
- "Type::LocalHash[#{ @id }]"
807
+ "Type::Local[#{ @kind }, #{ @id }, base_type: #{ @base_type.inspect }]"
795
808
  end
796
809
 
797
810
  def screen_name(scratch)
798
- #raise "LocalHash must not be included in signature"
799
- "LocalHash!"
811
+ #raise "Local type must not be included in signature"
812
+ "Local[#{ @kind }]"
800
813
  end
801
814
 
802
815
  def globalize(env, visited, depth)
@@ -808,16 +821,57 @@ module TypeProf
808
821
  if elems
809
822
  elems = elems.globalize(env, visited, depth - 1)
810
823
  else
811
- elems = Hash::Elements.new({Type.any => Type.any})
824
+ # TODO: currently out-of-scope array cannot be accessed
825
+ elems = @kind::Elements.dummy_elements
812
826
  end
813
827
  visited.delete(self)
814
- Hash.new(elems, @base_type)
828
+ @kind.new(elems, @base_type)
815
829
  end
816
830
  end
817
831
 
818
832
  def method_dispatch_info
819
833
  @base_type.method_dispatch_info
820
834
  end
835
+
836
+ def update_container_elem_type(subst, env, caller_ep, scratch)
837
+ case
838
+ when @kind == Cell
839
+ tyvars = @base_type.klass.type_params.map {|name,| Type::Var.new(name) }
840
+ # XXX: This should be skipped when the called methods belongs to superclass
841
+ tyvars.each_with_index do |tyvar, idx|
842
+ ty = subst[tyvar]
843
+ if ty
844
+ env, ty = scratch.localize_type(ty, env, caller_ep)
845
+ env = scratch.update_container_elem_types(env, caller_ep, @id, @base_type) do |elems|
846
+ elems.update(idx, ty)
847
+ end
848
+ end
849
+ end
850
+ when @kind == Array
851
+ tyvar_elem = Type::Var.new(:Elem)
852
+ if subst[tyvar_elem]
853
+ ty = subst[tyvar_elem]
854
+ env, ty = scratch.localize_type(ty, env, caller_ep)
855
+ env = scratch.update_container_elem_types(env, caller_ep, @id, @base_type) do |elems|
856
+ elems.update(nil, ty)
857
+ end
858
+ end
859
+ when @kind == Hash
860
+ tyvar_k = Type::Var.new(:K)
861
+ tyvar_v = Type::Var.new(:V)
862
+ if subst[tyvar_k] && subst[tyvar_v]
863
+ k_ty = subst[tyvar_k]
864
+ v_ty = subst[tyvar_v]
865
+ alloc_site = AllocationSite.new(caller_ep)
866
+ env, k_ty = scratch.localize_type(k_ty, env, caller_ep, alloc_site.add_id(:k))
867
+ env, v_ty = scratch.localize_type(v_ty, env, caller_ep, alloc_site.add_id(:v))
868
+ env = scratch.update_container_elem_types(env, caller_ep, @id, @base_type) do |elems|
869
+ elems.update(k_ty, v_ty)
870
+ end
871
+ end
872
+ end
873
+ env
874
+ end
821
875
  end
822
876
  end
823
877
  end