typeprof 0.21.11 → 0.30.1

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