typeprof 0.21.11 → 0.30.1

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 (72) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +15 -31
  3. data/bin/typeprof +5 -0
  4. data/doc/doc.ja.md +134 -0
  5. data/doc/doc.md +136 -0
  6. data/lib/typeprof/cli/cli.rb +178 -0
  7. data/lib/typeprof/cli.rb +3 -133
  8. data/lib/typeprof/code_range.rb +112 -0
  9. data/lib/typeprof/core/ast/base.rb +263 -0
  10. data/lib/typeprof/core/ast/call.rb +259 -0
  11. data/lib/typeprof/core/ast/const.rb +126 -0
  12. data/lib/typeprof/core/ast/control.rb +433 -0
  13. data/lib/typeprof/core/ast/meta.rb +150 -0
  14. data/lib/typeprof/core/ast/method.rb +339 -0
  15. data/lib/typeprof/core/ast/misc.rb +263 -0
  16. data/lib/typeprof/core/ast/module.rb +123 -0
  17. data/lib/typeprof/core/ast/pattern.rb +140 -0
  18. data/lib/typeprof/core/ast/sig_decl.rb +471 -0
  19. data/lib/typeprof/core/ast/sig_type.rb +663 -0
  20. data/lib/typeprof/core/ast/value.rb +319 -0
  21. data/lib/typeprof/core/ast/variable.rb +315 -0
  22. data/lib/typeprof/core/ast.rb +472 -0
  23. data/lib/typeprof/core/builtin.rb +146 -0
  24. data/lib/typeprof/core/env/method.rb +137 -0
  25. data/lib/typeprof/core/env/method_entity.rb +55 -0
  26. data/lib/typeprof/core/env/module_entity.rb +408 -0
  27. data/lib/typeprof/core/env/static_read.rb +155 -0
  28. data/lib/typeprof/core/env/type_alias_entity.rb +27 -0
  29. data/lib/typeprof/core/env/value_entity.rb +32 -0
  30. data/lib/typeprof/core/env.rb +366 -0
  31. data/lib/typeprof/core/graph/box.rb +998 -0
  32. data/lib/typeprof/core/graph/change_set.rb +224 -0
  33. data/lib/typeprof/core/graph/filter.rb +155 -0
  34. data/lib/typeprof/core/graph/vertex.rb +225 -0
  35. data/lib/typeprof/core/service.rb +514 -0
  36. data/lib/typeprof/core/type.rb +352 -0
  37. data/lib/typeprof/core/util.rb +81 -0
  38. data/lib/typeprof/core.rb +31 -0
  39. data/lib/typeprof/diagnostic.rb +35 -0
  40. data/lib/typeprof/lsp/messages.rb +415 -0
  41. data/lib/typeprof/lsp/server.rb +203 -0
  42. data/lib/typeprof/lsp/text.rb +69 -0
  43. data/lib/typeprof/lsp/util.rb +51 -0
  44. data/lib/typeprof/lsp.rb +4 -907
  45. data/lib/typeprof/version.rb +1 -1
  46. data/lib/typeprof.rb +4 -18
  47. data/typeprof.gemspec +5 -7
  48. metadata +47 -33
  49. data/.github/dependabot.yml +0 -6
  50. data/.github/workflows/main.yml +0 -39
  51. data/.gitignore +0 -9
  52. data/Gemfile +0 -17
  53. data/Gemfile.lock +0 -41
  54. data/Rakefile +0 -10
  55. data/exe/typeprof +0 -10
  56. data/lib/typeprof/analyzer.rb +0 -2598
  57. data/lib/typeprof/arguments.rb +0 -414
  58. data/lib/typeprof/block.rb +0 -176
  59. data/lib/typeprof/builtin.rb +0 -893
  60. data/lib/typeprof/code-range.rb +0 -177
  61. data/lib/typeprof/config.rb +0 -158
  62. data/lib/typeprof/container-type.rb +0 -912
  63. data/lib/typeprof/export.rb +0 -589
  64. data/lib/typeprof/import.rb +0 -852
  65. data/lib/typeprof/insns-def.rb +0 -65
  66. data/lib/typeprof/iseq.rb +0 -864
  67. data/lib/typeprof/method.rb +0 -355
  68. data/lib/typeprof/type.rb +0 -1140
  69. data/lib/typeprof/utils.rb +0 -212
  70. data/tools/coverage.rb +0 -14
  71. data/tools/setup-insns-def.rb +0 -30
  72. data/typeprof-lsp +0 -3
@@ -0,0 +1,514 @@
1
+ module TypeProf::Core
2
+ class Service
3
+ def initialize(options)
4
+ @options = options
5
+
6
+ @rb_text_nodes = {}
7
+ @rbs_text_nodes = {}
8
+
9
+ @genv = GlobalEnv.new
10
+ @genv.load_core_rbs(load_rbs_declarations(@options[:rbs_collection]).declarations)
11
+
12
+ Builtin.new(genv).deploy
13
+ end
14
+
15
+ def load_rbs_declarations(rbs_collection)
16
+ if rbs_collection
17
+ loader = RBS::EnvironmentLoader.new
18
+ loader.add_collection(rbs_collection)
19
+ RBS::Environment.from_loader(loader)
20
+ else
21
+ return $raw_rbs_env if defined?($raw_rbs_env)
22
+ loader = RBS::EnvironmentLoader.new
23
+ $raw_rbs_env = RBS::Environment.from_loader(loader)
24
+ end
25
+ end
26
+
27
+ attr_reader :genv
28
+
29
+ def reset!
30
+ @rb_text_nodes.each_value {|node| node.undefine(@genv) }
31
+ @rbs_text_nodes.each_value {|nodes| nodes.each {|n| n.undefine(@genv) } }
32
+ @genv.define_all
33
+ @rb_text_nodes.each_value {|node| node.uninstall(@genv) }
34
+ @rbs_text_nodes.each_value {|nodes| nodes.each {|n| n.uninstall(@genv) } }
35
+ @genv.run_all
36
+ @rb_text_nodes.clear
37
+ @rbs_text_nodes.clear
38
+ end
39
+
40
+ def add_workspace(rb_folder, rbs_folder)
41
+ Dir.glob(File.expand_path(rb_folder + "/**/*.{rb,rbs}")) do |path|
42
+ update_file(path, nil)
43
+ end
44
+ end
45
+
46
+ def update_file(path, code)
47
+ if File.extname(path) == ".rbs"
48
+ update_rbs_file(path, code)
49
+ else
50
+ update_rb_file(path, code)
51
+ end
52
+ end
53
+
54
+ def update_rb_file(path, code)
55
+ prev_node = @rb_text_nodes[path]
56
+
57
+ code = File.read(path) unless code
58
+ node = AST.parse_rb(path, code)
59
+ return false unless node
60
+
61
+ node.diff(@rb_text_nodes[path]) if prev_node
62
+ @rb_text_nodes[path] = node
63
+
64
+ node.define(@genv)
65
+ prev_node.undefine(@genv) if prev_node
66
+ @genv.define_all
67
+
68
+ node.install(@genv)
69
+ prev_node.uninstall(@genv) if prev_node
70
+ @genv.run_all
71
+
72
+ # invariant validation
73
+ if prev_node
74
+ live_vtxs = []
75
+ node.get_vertexes(live_vtxs)
76
+ set = Set[]
77
+ live_vtxs.uniq.each {|vtx| set << vtx }
78
+ live_vtxs = set
79
+
80
+ dead_vtxs = []
81
+ prev_node.get_vertexes(dead_vtxs)
82
+ set = Set[]
83
+ dead_vtxs.uniq.each {|vtx| set << vtx }
84
+ dead_vtxs = set
85
+
86
+ live_vtxs.each do |vtx|
87
+ next unless vtx
88
+ raise vtx.inspect if dead_vtxs.include?(vtx)
89
+ end
90
+
91
+ global_vtxs = []
92
+ @genv.get_vertexes(global_vtxs)
93
+ set = Set[]
94
+ global_vtxs.uniq.each {|vtx| set << vtx }
95
+ global_vtxs = set
96
+
97
+ global_vtxs.each do |global_vtx|
98
+ next unless global_vtx.is_a?(Vertex)
99
+ raise if dead_vtxs.include?(global_vtx)
100
+ global_vtx.types.each_value do |prev_vtxs|
101
+ prev_vtxs.each do |prev_vtx|
102
+ raise if dead_vtxs.include?(prev_vtx)
103
+ end
104
+ end
105
+ global_vtx.next_vtxs.each do |next_vtx|
106
+ raise "#{ next_vtx }" if dead_vtxs.include?(next_vtx)
107
+ end
108
+ end
109
+ end
110
+
111
+ return true
112
+ end
113
+
114
+ def update_rbs_file(path, code)
115
+ prev_decls = @rbs_text_nodes[path]
116
+
117
+ code = File.read(path) unless code
118
+ begin
119
+ decls = AST.parse_rbs(path, code)
120
+ rescue RBS::ParsingError
121
+ return false
122
+ end
123
+
124
+ # TODO: diff
125
+ @rbs_text_nodes[path] = decls
126
+
127
+ decls.each {|decl| decl.define(@genv) }
128
+ prev_decls.each {|decl| decl.undefine(@genv) } if prev_decls
129
+ @genv.define_all
130
+
131
+ decls.each {|decl| decl.install(@genv) }
132
+ prev_decls.each {|decl| decl.uninstall(@genv) } if prev_decls
133
+ @genv.run_all
134
+
135
+ true
136
+ end
137
+
138
+ def diagnostics(path, &blk)
139
+ @rb_text_nodes[path]&.diagnostics(@genv, &blk)
140
+ end
141
+
142
+ def definitions(path, pos)
143
+ defs = []
144
+ @rb_text_nodes[path]&.retrieve_at(pos) do |node|
145
+ node.boxes(:cread) do |box|
146
+ if box.const_read && box.const_read.cdef
147
+ box.const_read.cdef.defs.each do |cdef_node|
148
+ defs << [cdef_node.lenv.path, cdef_node.cname_code_range]
149
+ end
150
+ end
151
+ end
152
+ node.boxes(:mcall) do |box|
153
+ boxes = []
154
+ box.changes.boxes.each do |key, box|
155
+ # ad-hocly handle Class#new calls
156
+ if key[0] == :mcall && key[3] == :initialize # XXX: better condition?
157
+ boxes << box
158
+ end
159
+ end
160
+ boxes << box if boxes.empty?
161
+ boxes.each do |box|
162
+ box.resolve(genv, nil) do |me, _ty, mid, _orig_ty|
163
+ next unless me
164
+ me.defs.each do |mdef|
165
+ code_range =
166
+ if mdef.node.respond_to?(:mname_code_range)
167
+ mdef.node.mname_code_range(mid)
168
+ else
169
+ mdef.node.code_range
170
+ end
171
+
172
+ defs << [mdef.node.lenv.path, code_range]
173
+ end
174
+ end
175
+ end
176
+ end
177
+ return defs unless defs.empty?
178
+ end
179
+ return defs
180
+ end
181
+
182
+ def type_definitions(path, pos)
183
+ @rb_text_nodes[path]&.retrieve_at(pos) do |node|
184
+ if node.ret
185
+ ty_defs = []
186
+ node.ret.types.map do |ty, _source|
187
+ if ty.is_a?(Type::Instance)
188
+ ty.mod.module_decls.each do |mdecl|
189
+ # TODO
190
+ end
191
+ ty.mod.module_defs.each do |mdef_node|
192
+ ty_defs << [mdef_node.lenv.path, mdef_node.code_range]
193
+ end
194
+ end
195
+ end
196
+ return ty_defs
197
+ end
198
+ end
199
+ []
200
+ end
201
+
202
+ #: (String, TypeProf::CodePosition) -> Array[[String?, TypeProf::CodeRange]]?
203
+ def references(path, pos)
204
+ refs = []
205
+ @rb_text_nodes[path]&.retrieve_at(pos) do |node|
206
+ case node
207
+ when AST::DefNode
208
+ if node.mid_code_range.include?(pos)
209
+ node.boxes(:mdef) do |mdef|
210
+ me = @genv.resolve_method(mdef.cpath, mdef.singleton, mdef.mid)
211
+ if me
212
+ me.method_call_boxes.each do |box|
213
+ node = box.node
214
+ refs << [node.lenv.path, node.code_range]
215
+ end
216
+ end
217
+ end
218
+ end
219
+ # TODO: Callsite
220
+ when AST::ConstantReadNode
221
+ if node.cname_code_range.include?(pos)
222
+ node.boxes(:cread) do |cread_box|
223
+ @genv.resolve_const(cread_box.const_read.cpath).read_boxes.each do |box|
224
+ node = box.node
225
+ refs << [node.lenv.path, node.code_range]
226
+ end
227
+ end
228
+ end
229
+ when AST::ConstantWriteNode
230
+ if node.cname_code_range && node.cname_code_range.include?(pos) && node.static_cpath
231
+ @genv.resolve_const(node.static_cpath).read_boxes.each do |box|
232
+ node = box.node
233
+ refs << [node.lenv.path, node.code_range]
234
+ end
235
+ end
236
+ end
237
+ end
238
+ refs = refs.uniq
239
+ return refs.empty? ? nil : refs
240
+ end
241
+
242
+ def rename(path, pos)
243
+ mdefs = []
244
+ cdefs = []
245
+ @rb_text_nodes[path]&.retrieve_at(pos) do |node|
246
+ node.boxes(:mcall) do |box|
247
+ box.resolve(genv, nil) do |me, _ty, _mid, _orig_ty|
248
+ next unless me
249
+ me.defs.each do |mdef|
250
+ mdefs << mdef
251
+ end
252
+ end
253
+ end
254
+ node.boxes(:cread) do |box|
255
+ if box.node.cname_code_range.include?(pos)
256
+ box.const_read.cdef.defs.each do |cdef|
257
+ cdefs << cdef
258
+ end
259
+ end
260
+ end
261
+ if node.is_a?(AST::DefNode) && node.mid_code_range.include?(pos)
262
+ node.boxes(:mdef) do |mdef|
263
+ mdefs << mdef
264
+ end
265
+ end
266
+ end
267
+ targets = []
268
+ mdefs.each do |mdef|
269
+ # TODO: support all method definition nodes rather than defn/defs (e.g., attr_reader, alias, SIG_DEF, etc.)
270
+ targets << [mdef.node.lenv.path, mdef.node.mid_code_range]
271
+ me = @genv.resolve_method(mdef.cpath, mdef.singleton, mdef.mid)
272
+ if me
273
+ me.method_call_boxes.each do |box|
274
+ # TODO: if it is a super node, we need to change its method name too
275
+ targets << [box.node.lenv.path, box.node.mid_code_range]
276
+ end
277
+ end
278
+ end
279
+ cdefs.each do |cdef|
280
+ if cdef.is_a?(AST::ConstantWriteNode)
281
+ targets << [cdef.lenv.path, cdef.cname_code_range] if cdef.cname_code_range
282
+ end
283
+ ve = @genv.resolve_const(cdef.static_cpath)
284
+ ve.read_boxes.each do |box|
285
+ targets << [box.node.lenv.path, box.node.cname_code_range]
286
+ end
287
+ end
288
+ if targets.all? {|_path, cr| cr }
289
+ targets.uniq
290
+ else
291
+ # TODO: report an error
292
+ nil
293
+ end
294
+ end
295
+
296
+ def hover(path, pos)
297
+ @rb_text_nodes[path]&.retrieve_at(pos) do |node|
298
+ node.boxes(:mcall) do |box|
299
+ boxes = []
300
+ box.changes.boxes.each do |key, box|
301
+ # ad-hocly handle Class#new calls
302
+ if key[0] == :mcall && key[3] == :initialize # XXX: better condition?
303
+ boxes << box
304
+ end
305
+ end
306
+ boxes << box if boxes.empty?
307
+ boxes.each do |box|
308
+ box.resolve(genv, nil) do |me, ty, mid, orig_ty|
309
+ if me
310
+ if !me.decls.empty?
311
+ me.decls.each do |mdecl|
312
+ return "#{ orig_ty.show }##{ mid } : #{ mdecl.show }"
313
+ end
314
+ end
315
+ if !me.defs.empty?
316
+ me.defs.each do |mdef|
317
+ return "#{ orig_ty.show }##{ mid } : #{ mdef.show(@options[:output_parameter_names]) }"
318
+ end
319
+ end
320
+ end
321
+ end
322
+ end
323
+ return "??? failed to hover"
324
+ end
325
+ return node.ret ? node.ret.show : "??? no type ???"
326
+ end
327
+ end
328
+
329
+ def code_lens(path)
330
+ cpaths = []
331
+ @rb_text_nodes[path]&.traverse do |event, node|
332
+ if node.is_a?(AST::ModuleBaseNode)
333
+ if node.static_cpath
334
+ if event == :enter
335
+ cpaths << node.static_cpath
336
+ else
337
+ cpaths.pop
338
+ end
339
+ end
340
+ else
341
+ if event == :enter
342
+ next if node.is_a?(AST::DefNode) && node.rbs_method_type
343
+ node.boxes(:mdef) do |mdef|
344
+ hint = mdef.show(@options[:output_parameter_names])
345
+ if hint
346
+ yield mdef.node.code_range, hint
347
+ end
348
+ end
349
+ end
350
+ end
351
+ end
352
+ end
353
+
354
+ def completion(path, trigger, pos)
355
+ @rb_text_nodes[path]&.retrieve_at(pos) do |node|
356
+ if node.code_range.last == pos.right
357
+ node.ret.types.map do |ty, _source|
358
+ base_ty = ty.base_type(genv)
359
+
360
+ @genv.each_superclass(base_ty.mod, base_ty.is_a?(Type::Singleton)) do |mod, singleton|
361
+ mod.methods[singleton].each do |mid, me|
362
+ sig = nil
363
+ me.decls.each do |mdecl|
364
+ sig = mdecl.method_types.map {|method_type| method_type.instance_variable_get(:@raw_node).to_s }.join(" | ")
365
+ break
366
+ end
367
+ unless sig
368
+ me.defs.each do |mdef|
369
+ sig = mdef.show(@options[:output_parameter_names])
370
+ break
371
+ end
372
+ end
373
+ yield mid, "#{ mod.cpath.join("::" )}#{ singleton ? "." : "#" }#{ mid } : #{ sig }" if sig
374
+ end
375
+ end
376
+ end
377
+ return
378
+ end
379
+ end
380
+ end
381
+
382
+ def dump_declarations(path)
383
+ stack = []
384
+ out = []
385
+ @rb_text_nodes[path]&.traverse do |event, node|
386
+ case node
387
+ when AST::ModuleNode
388
+ if node.static_cpath
389
+ if event == :enter
390
+ out << " " * stack.size + "module #{ node.static_cpath.join("::") }"
391
+ if stack == [:toplevel]
392
+ out << "end"
393
+ stack.pop
394
+ end
395
+ stack.push(node)
396
+ else
397
+ stack.pop
398
+ out << " " * stack.size + "end"
399
+ end
400
+ end
401
+ when AST::ClassNode, AST::SingletonClassNode
402
+ if node.static_cpath
403
+ next if stack.any? { node.is_a?(AST::SingletonClassNode) && (_1.is_a?(AST::ClassNode) || _1.is_a?(AST::ModuleNode)) && node.static_cpath == _1.static_cpath }
404
+
405
+ if event == :enter
406
+ s = "class #{ node.static_cpath.join("::") }"
407
+ mod = @genv.resolve_cpath(node.static_cpath)
408
+ superclass = mod.superclass
409
+ if superclass == nil
410
+ s << " # failed to identify its superclass"
411
+ elsif superclass.cpath != []
412
+ s << " < #{ superclass.show_cpath }"
413
+ end
414
+ if stack == [:toplevel]
415
+ out << "end"
416
+ stack.pop
417
+ end
418
+ out << " " * stack.size + s
419
+ stack.push(node)
420
+ mod.included_modules.each do |inc_def, inc_mod|
421
+ if inc_def.is_a?(AST::ConstantReadNode) && inc_def.lenv.path == path
422
+ out << " " * stack.size + "include #{ inc_mod.show_cpath }"
423
+ end
424
+ end
425
+ else
426
+ stack.pop
427
+ out << " " * stack.size + "end"
428
+ end
429
+ end
430
+ when AST::ConstantWriteNode
431
+ if node.static_cpath
432
+ if event == :enter
433
+ out << " " * stack.size + "#{ node.static_cpath.join("::") }: #{ node.ret.show }"
434
+ end
435
+ end
436
+ else
437
+ if event == :enter
438
+ node.boxes(:mdef) do |mdef|
439
+ if stack.empty?
440
+ out << " " * stack.size + "class Object"
441
+ stack << :toplevel
442
+ end
443
+ if @options[:output_source_locations]
444
+ pos = mdef.node.code_range.first
445
+ out << " " * stack.size + "# #{ path }:#{ pos.lineno }:#{ pos.column + 1 }"
446
+ end
447
+ out << " " * stack.size + "def #{ mdef.singleton ? "self." : "" }#{ mdef.mid }: " + mdef.show(@options[:output_parameter_names])
448
+ end
449
+ end
450
+ end
451
+ end
452
+ if stack == [:toplevel]
453
+ out << "end"
454
+ stack.pop
455
+ end
456
+ out.join("\n") + "\n"
457
+ end
458
+
459
+ def get_method_sig(cpath, singleton, mid)
460
+ s = []
461
+ @genv.resolve_method(cpath, singleton, mid).defs.each do |mdef|
462
+ s << "def #{ mid }: " + mdef.show
463
+ end
464
+ s
465
+ end
466
+
467
+ def batch(files, output)
468
+ if @options[:output_typeprof_version]
469
+ output.puts "# TypeProf #{ TypeProf::VERSION }"
470
+ output.puts
471
+ end
472
+
473
+ i = 0
474
+ show_files = files.select do |file|
475
+ if @options[:display_indicator]
476
+ $stderr << "\r[%d/%d] %s\e[K" % [i, files.size, file]
477
+ i += 1
478
+ end
479
+
480
+ res = update_file(file, File.read(file))
481
+
482
+ if res
483
+ true
484
+ else
485
+ output.puts "# failed to analyze: #{ file }"
486
+ false
487
+ end
488
+ end
489
+ if @options[:display_indicator]
490
+ $stderr << "\r\e[K"
491
+ end
492
+
493
+ first = true
494
+ show_files.each do |file|
495
+ next if File.extname(file) == ".rbs"
496
+ output.puts unless first
497
+ first = false
498
+ output.puts "# #{ file }"
499
+ if @options[:output_diagnostics]
500
+ diagnostics(file) do |diag|
501
+ output.puts "# #{ diag.code_range.to_s }:#{ diag.msg }"
502
+ end
503
+ end
504
+ output.puts dump_declarations(file)
505
+ end
506
+ end
507
+ end
508
+ end
509
+
510
+ if $0 == __FILE__
511
+ core = TypeProf::Core::Service.new({})
512
+ core.add_workspaces(["foo"].to_a)
513
+ core.update_rb_file("foo", "foo")
514
+ end