typeprof 0.15.3 → 0.20.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -624,7 +624,7 @@ module TypeProf
624
624
  end
625
625
 
626
626
  def self.file_load(path, ep, env, scratch, &ctn)
627
- iseq = ISeq.compile(path)
627
+ iseq, = ISeq.compile(path)
628
628
  callee_ep, callee_env = TypeProf.starting_state(iseq)
629
629
  scratch.merge_env(callee_ep, callee_env)
630
630
 
@@ -793,27 +793,27 @@ module TypeProf
793
793
 
794
794
  Import.import_builtin(scratch)
795
795
 
796
- Type::Builtin[:vmcore] = scratch.new_class(klass_obj, :VMCore, [], klass_obj, nil)
797
- Type::Builtin[:int] = scratch.get_constant(klass_obj, :Integer)
798
- Type::Builtin[:float] = scratch.get_constant(klass_obj, :Float)
799
- Type::Builtin[:rational] = scratch.get_constant(klass_obj, :Rational)
800
- Type::Builtin[:complex] = scratch.get_constant(klass_obj, :Complex)
801
- Type::Builtin[:sym] = scratch.get_constant(klass_obj, :Symbol)
802
- Type::Builtin[:str] = scratch.get_constant(klass_obj, :String)
803
- Type::Builtin[:struct] = scratch.get_constant(klass_obj, :Struct)
804
- Type::Builtin[:ary] = scratch.get_constant(klass_obj, :Array)
805
- Type::Builtin[:hash] = scratch.get_constant(klass_obj, :Hash)
806
- Type::Builtin[:io] = scratch.get_constant(klass_obj, :IO)
807
- Type::Builtin[:proc] = scratch.get_constant(klass_obj, :Proc)
808
- Type::Builtin[:range] = scratch.get_constant(klass_obj, :Range)
809
- Type::Builtin[:regexp] = scratch.get_constant(klass_obj, :Regexp)
810
- Type::Builtin[:matchdata] = scratch.get_constant(klass_obj, :MatchData)
811
- Type::Builtin[:class] = scratch.get_constant(klass_obj, :Class)
812
- Type::Builtin[:module] = scratch.get_constant(klass_obj, :Module)
813
- Type::Builtin[:exc] = scratch.get_constant(klass_obj, :Exception)
814
- Type::Builtin[:encoding] = scratch.get_constant(klass_obj, :Encoding)
815
- Type::Builtin[:enumerator] = scratch.get_constant(klass_obj, :Enumerator)
816
- Type::Builtin[:kernel] = scratch.get_constant(klass_obj, :Kernel)
796
+ Type::Builtin[:vmcore] , = scratch.new_class(klass_obj, :VMCore, [], klass_obj, nil)
797
+ Type::Builtin[:int] , = scratch.get_constant(klass_obj, :Integer)
798
+ Type::Builtin[:float] , = scratch.get_constant(klass_obj, :Float)
799
+ Type::Builtin[:rational] , = scratch.get_constant(klass_obj, :Rational)
800
+ Type::Builtin[:complex] , = scratch.get_constant(klass_obj, :Complex)
801
+ Type::Builtin[:sym] , = scratch.get_constant(klass_obj, :Symbol)
802
+ Type::Builtin[:str] , = scratch.get_constant(klass_obj, :String)
803
+ Type::Builtin[:struct] , = scratch.get_constant(klass_obj, :Struct)
804
+ Type::Builtin[:ary] , = scratch.get_constant(klass_obj, :Array)
805
+ Type::Builtin[:hash] , = scratch.get_constant(klass_obj, :Hash)
806
+ Type::Builtin[:io] , = scratch.get_constant(klass_obj, :IO)
807
+ Type::Builtin[:proc] , = scratch.get_constant(klass_obj, :Proc)
808
+ Type::Builtin[:range] , = scratch.get_constant(klass_obj, :Range)
809
+ Type::Builtin[:regexp] , = scratch.get_constant(klass_obj, :Regexp)
810
+ Type::Builtin[:matchdata] , = scratch.get_constant(klass_obj, :MatchData)
811
+ Type::Builtin[:class] , = scratch.get_constant(klass_obj, :Class)
812
+ Type::Builtin[:module] , = scratch.get_constant(klass_obj, :Module)
813
+ Type::Builtin[:exc] , = scratch.get_constant(klass_obj, :Exception)
814
+ Type::Builtin[:encoding] , = scratch.get_constant(klass_obj, :Encoding)
815
+ Type::Builtin[:enumerator] , = scratch.get_constant(klass_obj, :Enumerator)
816
+ Type::Builtin[:kernel] , = scratch.get_constant(klass_obj, :Kernel)
817
817
 
818
818
  klass_vmcore = Type::Builtin[:vmcore]
819
819
  klass_ary = Type::Builtin[:ary]
@@ -881,7 +881,7 @@ module TypeProf
881
881
  # ENV: Hash[String, String]
882
882
  str_ty = Type::Instance.new(Type::Builtin[:str])
883
883
  env_ty = Type.gen_hash {|h| h[str_ty] = Type.optional(str_ty) }
884
- scratch.add_constant(klass_obj, :ENV, env_ty, false)
884
+ scratch.add_constant(klass_obj, :ENV, env_ty, nil)
885
885
 
886
886
  scratch.search_method(Type::Builtin[:kernel], false, :sprintf) do |mdefs,|
887
887
  mdefs.each do |mdef|
data/lib/typeprof/cli.rb CHANGED
@@ -18,11 +18,15 @@ module TypeProf
18
18
  verbose = 1
19
19
 
20
20
  options = {}
21
+ lsp_options = {}
21
22
  dir_filter = nil
22
23
  gem_rbs_features = []
23
24
  gem_repo_dirs = []
24
25
  show_version = false
25
26
  max_sec = max_iter = nil
27
+ collection_path = RBS::Collection::Config::PATH
28
+
29
+ load_path_ext = []
26
30
 
27
31
  opt.separator ""
28
32
  opt.separator "Options:"
@@ -30,9 +34,12 @@ module TypeProf
30
34
  opt.on("-q", "--quiet", "Do not display progress indicator") { options[:show_indicator] = false }
31
35
  opt.on("-v", "--verbose", "Alias to --show-errors") { options[:show_errors] = true }
32
36
  opt.on("--version", "Display typeprof version") { show_version = true }
33
- opt.on("-I DIR", "Add DIR to the load/require path") {|v| $LOAD_PATH << v }
37
+ opt.on("-I DIR", "Add DIR to the load/require path") {|v| load_path_ext << v }
34
38
  opt.on("-r FEATURE", "Require RBS of the FEATURE gem") {|v| gem_rbs_features << v }
35
39
  opt.on("--repo DIR", "Add DIR to the RBS repository") {|v| gem_repo_dirs << v }
40
+ opt.on("--collection PATH", "File path of collection configuration") { |v| collection_path = v }
41
+ opt.on("--no-collection", "Ignore collection configuration") { collection_path = nil }
42
+ opt.on("--lsp", "LSP mode") {|v| options[:lsp] = true }
36
43
 
37
44
  opt.separator ""
38
45
  opt.separator "Analysis output options:"
@@ -68,8 +75,14 @@ module TypeProf
68
75
  opt.on("--debug", "Display analysis log (for debugging purpose)") { verbose = 2 }
69
76
  opt.on("--[no-]stackprof MODE", /\Acpu|wall|object\z/, "Enable stackprof (for debugging purpose)") {|v| options[:stackprof] = v.to_sym }
70
77
 
78
+ opt.separator ""
79
+ opt.separator "LSP options:"
80
+ opt.on("--port PORT", Integer, "Specify a port number to listen for requests on") {|v| lsp_options[:port] = v }
81
+
71
82
  opt.parse!(argv)
72
83
 
84
+ $LOAD_PATH.unshift(*load_path_ext)
85
+
73
86
  dir_filter ||= ConfigData::DEFAULT_DIR_FILTER
74
87
  rb_files = []
75
88
  rbs_files = []
@@ -82,22 +95,29 @@ module TypeProf
82
95
  end
83
96
 
84
97
  puts "typeprof #{ VERSION }" if show_version
85
- if rb_files.empty?
98
+ if rb_files.empty? && !options[:lsp]
86
99
  exit if show_version
87
100
  raise OptionParser::InvalidOption.new("no input files")
88
101
  end
89
102
 
103
+ if !options[:lsp] && !lsp_options.empty?
104
+ exit if show_version
105
+ raise OptionParser::InvalidOption.new("lsp options with non-lsp mode")
106
+ end
107
+
90
108
  ConfigData.new(
91
109
  rb_files: rb_files,
92
110
  rbs_files: rbs_files,
93
111
  output: output,
94
112
  gem_rbs_features: gem_rbs_features,
95
113
  gem_repo_dirs: gem_repo_dirs,
114
+ collection_path: collection_path,
96
115
  verbose: verbose,
97
116
  dir_filter: dir_filter,
98
117
  max_sec: max_sec,
99
118
  max_iter: max_iter,
100
119
  options: options,
120
+ lsp_options: lsp_options,
101
121
  )
102
122
 
103
123
  rescue OptionParser::InvalidOption
@@ -0,0 +1,177 @@
1
+ module TypeProf
2
+ class CodeLocation
3
+ # In Ruby, lineno is 1-origin, and column is 0-origin
4
+ def initialize(lineno, column)
5
+ @lineno = lineno
6
+ @column = column
7
+ end
8
+
9
+ def inspect
10
+ "(%d,%d)" % [@lineno, @column]
11
+ end
12
+
13
+ attr_reader :lineno, :column
14
+
15
+ def self.from_lsp(lsp_loc)
16
+ # In the Language Server Protocol, lineno and column are both 0-origin
17
+ CodeLocation.new(lsp_loc[:line] + 1, lsp_loc[:character])
18
+ end
19
+
20
+ def to_lsp
21
+ { line: @lineno - 1, character: @column }
22
+ end
23
+
24
+ def advance_cursor(offset, source_text)
25
+ new_lineno = @lineno
26
+ new_column = @column
27
+ while offset > 0
28
+ line_text = source_text.lines[new_lineno - 1]
29
+ if new_column + offset >= line_text.length
30
+ advanced = line_text.length - new_column
31
+ offset -= advanced
32
+ new_lineno += 1
33
+ new_column = 0
34
+ else
35
+ new_column += offset
36
+ break
37
+ end
38
+ end
39
+ CodeLocation.new(new_lineno, new_column)
40
+ end
41
+
42
+ def <=>(other)
43
+ ret = @lineno <=> other.lineno
44
+ return ret if ret != 0
45
+ @column <=> other.column
46
+ end
47
+
48
+ include Comparable
49
+ end
50
+
51
+ class CodeRange
52
+ def initialize(first, last)
53
+ @first, @last = first, last
54
+ end
55
+
56
+ def inspect
57
+ "%p-%p" % [@first, @last]
58
+ end
59
+
60
+ attr_reader :first
61
+ attr_reader :last
62
+
63
+ def self.from_lsp(lsp_range)
64
+ CodeRange.new(CodeLocation.from_lsp(lsp[:start]), CodeLocation.from_lsp(lsp[:end]))
65
+ end
66
+
67
+ def self.from_rbs(rbs_loc)
68
+ CodeRange.new(
69
+ CodeLocation.new(rbs_loc.start_line, rbs_loc.start_column),
70
+ CodeLocation.new(rbs_loc.end_line, rbs_loc.end_column),
71
+ )
72
+ end
73
+
74
+ def to_lsp
75
+ { start: @first.to_lsp, end: @last.to_lsp }
76
+ end
77
+
78
+ def contain_loc?(loc)
79
+ @first <= loc && loc < @last
80
+ end
81
+
82
+ def contain?(other)
83
+ @first <= other.first && other.last <= @last
84
+ end
85
+
86
+ def overlap?(other)
87
+ if @first <= other.first
88
+ return @last > other.first
89
+ else
90
+ return @first < other.last
91
+ end
92
+ end
93
+ end
94
+
95
+ class CodeRangeTable
96
+ Entry = Struct.new(:range, :value, :children)
97
+
98
+ class Entry
99
+ def inspect
100
+ "[%p, %p, %p]" % [range, value, children]
101
+ end
102
+ end
103
+
104
+ def initialize(list = [])
105
+ @list = list # Array[Entry]
106
+ end
107
+
108
+ def []=(range, value)
109
+ i_b = @list.bsearch_index {|e| e.range.last > range.first } || @list.size
110
+ i_e = @list.bsearch_index {|e| e.range.first >= range.last } || @list.size
111
+ if i_b < i_e
112
+ # for all i in i_b...i_e, @list[i] overlaps with the range
113
+ if i_e - i_b == 1
114
+ if range.contain?(@list[i_b].range)
115
+ @list[i_b] = Entry[range, value, CodeRangeTable.new(@list[i_b, 1])]
116
+ elsif @list[i_b].range.contain?(range)
117
+ @list[i_b].children[range] = value
118
+ else
119
+ raise
120
+ end
121
+ else
122
+ if range.contain?(@list[i_b].range) && range.contain?(@list[i_e - 1].range)
123
+ @list[i_b...i_e] = [Entry[range, value, CodeRangeTable.new(@list[i_b...i_e])]]
124
+ else
125
+ raise
126
+ end
127
+ end
128
+ else
129
+ @list[i_b, 0] = [Entry[range, value, CodeRangeTable.new]]
130
+ end
131
+ end
132
+
133
+ def [](loc)
134
+ e = @list.bsearch {|e| e.range.last > loc }
135
+ if e && e.range.contain_loc?(loc)
136
+ return e.children[loc] || e.value
137
+ end
138
+ return nil
139
+ end
140
+ end
141
+ end
142
+
143
+ if $0 == __FILE__
144
+ include TypeProf
145
+ cr1 = CodeRange.new(CodeLocation.new(1, 0), CodeLocation.new(1, 2))
146
+ cr2 = CodeRange.new(CodeLocation.new(1, 2), CodeLocation.new(1, 4))
147
+ cr3 = CodeRange.new(CodeLocation.new(2, 0), CodeLocation.new(2, 2))
148
+ cr4 = CodeRange.new(CodeLocation.new(2, 3), CodeLocation.new(2, 5))
149
+ cr1and2 = CodeRange.new(CodeLocation.new(1, 0), CodeLocation.new(1, 5))
150
+ cr3and4 = CodeRange.new(CodeLocation.new(2, 0), CodeLocation.new(2, 5))
151
+ [[cr1, "A"], [cr2, "B"], [cr3, "C"], [cr4, "D"], [cr1and2, "AB"], [cr3and4, "CD"]].permutation do |ary|
152
+ tbl = CodeRangeTable.new
153
+ ary.each do |cr, v|
154
+ tbl[cr] = v
155
+ end
156
+ values = []
157
+ [1, 2].each do |lineno|
158
+ (0..5).each do |column|
159
+ values << tbl[CodeLocation.new(lineno, column)]
160
+ end
161
+ end
162
+ raise if values != ["A", "A", "B", "B", "AB", nil, "C", "C", "CD", "D", "D", nil]
163
+ end
164
+
165
+ source = <<~EOS
166
+ AB
167
+ CDE
168
+ F
169
+ EOS
170
+ a_loc = CodeLocation.new(1, 0)
171
+ b_loc = a_loc.advance_cursor(1, source)
172
+ raise unless b_loc.inspect == "(1,1)"
173
+ c_loc = a_loc.advance_cursor(3, source)
174
+ raise unless c_loc.inspect == "(2,0)"
175
+ f_loc = c_loc.advance_cursor(4, source)
176
+ raise unless f_loc.inspect == "(3,0)"
177
+ end
@@ -7,11 +7,14 @@ module TypeProf
7
7
  :output,
8
8
  :gem_rbs_features,
9
9
  :gem_repo_dirs,
10
+ :collection_path,
10
11
  :verbose,
11
12
  :dir_filter,
12
13
  :max_iter,
13
14
  :max_sec,
14
15
  :options,
16
+ :lsp_options,
17
+ :lsp,
15
18
  keyword_init: true
16
19
  )
17
20
 
@@ -43,6 +46,9 @@ module TypeProf
43
46
  union_width_limit: 10,
44
47
  stackprof: nil,
45
48
  }.merge(opt[:options])
49
+ opt[:lsp_options] = {
50
+ port: 0,
51
+ }.merge(opt[:lsp_options] || {})
46
52
  super(**opt)
47
53
  end
48
54
 
@@ -61,29 +67,39 @@ module TypeProf
61
67
  ]
62
68
  end
63
69
 
64
- def self.analyze(config)
65
- # Deploy the config to the TypeProf::Config (Note: This is thread unsafe)
66
- if TypeProf.const_defined?(:Config)
67
- TypeProf.send(:remove_const, :Config)
70
+ module Config
71
+ def self.current
72
+ Thread.current[:typeprof_config]
68
73
  end
69
- TypeProf.const_set(:Config, config)
70
74
 
71
- if Config.options[:stackprof]
75
+ def self.set_current(config)
76
+ Thread.current[:typeprof_config] = config
77
+ end
78
+ end
79
+
80
+ def self.analyze(config, cancel_token = nil)
81
+ # Deploy the config to the TypeProf::Config (Note: This is thread local)
82
+ Config.set_current(config)
83
+
84
+ if Config.current.options[:stackprof]
72
85
  require "stackprof"
73
- out = "typeprof-stackprof-#{ Config.options[:stackprof] }.dump"
74
- StackProf.start(mode: Config.options[:stackprof], out: out, raw: true)
86
+ out = "typeprof-stackprof-#{ Config.current.options[:stackprof] }.dump"
87
+ StackProf.start(mode: Config.current.options[:stackprof], out: out, raw: true)
75
88
  end
76
89
 
77
90
  scratch = Scratch.new
78
91
  Builtin.setup_initial_global_env(scratch)
79
92
 
80
- Config.gem_rbs_features.each do |feature|
93
+ Config.current.gem_rbs_features.each do |feature|
81
94
  Import.import_library(scratch, feature)
82
95
  end
83
96
 
97
+ collection_path = config.collection_path
98
+ Import.import_rbs_collection(scratch, collection_path) if collection_path&.exist?
99
+
84
100
  rbs_files = []
85
101
  rbs_codes = []
86
- Config.rbs_files.each do |rbs|
102
+ Config.current.rbs_files.each do |rbs|
87
103
  if rbs.is_a?(Array) # [String name, String content]
88
104
  rbs_codes << rbs
89
105
  else
@@ -95,30 +111,39 @@ module TypeProf
95
111
  Import.import_rbs_code(scratch, name, content)
96
112
  end
97
113
 
98
- Config.rb_files.each do |rb|
114
+ def_code_range_table = nil
115
+ caller_code_range_table = nil
116
+ Config.current.rb_files.each do |rb|
99
117
  if rb.is_a?(Array) # [String name, String content]
100
- iseq = ISeq.compile_str(*rb.reverse)
118
+ iseq, def_tbl, caller_tbl = ISeq.compile_str(*rb.reverse)
119
+ def_code_range_table ||= def_tbl
120
+ caller_code_range_table ||= caller_tbl
101
121
  else
102
122
  iseq = rb
103
123
  end
104
124
  scratch.add_entrypoint(iseq)
105
125
  end
106
126
 
107
- result = scratch.type_profile
127
+ result = scratch.type_profile(cancel_token)
128
+
129
+ if Config.current.options[:lsp]
130
+ return scratch.report_lsp, def_code_range_table, caller_code_range_table
131
+ end
108
132
 
109
- if Config.output.respond_to?(:write)
110
- scratch.report(result, Config.output)
133
+ if Config.current.output.respond_to?(:write)
134
+ scratch.report(result, Config.current.output)
111
135
  else
112
- open(Config.output, "w") do |output|
136
+ open(Config.current.output, "w") do |output|
113
137
  scratch.report(result, output)
114
138
  end
115
139
  end
116
140
 
117
141
  rescue TypeProfError => exc
118
- exc.report(Config.output)
142
+ exc.report(Config.current.output)
119
143
 
144
+ return nil
120
145
  ensure
121
- if Config.options[:stackprof] && defined?(StackProf)
146
+ if Config.current.options[:stackprof] && defined?(StackProf)
122
147
  StackProf.stop
123
148
  StackProf.results
124
149
  end
@@ -321,6 +321,9 @@ module TypeProf
321
321
  end
322
322
 
323
323
  "*[#{ squash.screen_name(scratch) }]"
324
+ rescue SystemStackError
325
+ p squash
326
+ exit!
324
327
  end
325
328
 
326
329
  def pretty_print(q)