typeprof 0.21.11 → 0.30.0

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