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