typeprof 0.15.3 → 0.20.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.
@@ -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)