typeprof 0.6.1 → 0.7.0

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