typeprof 0.15.0 → 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.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/main.yml +1 -1
  3. data/Gemfile.lock +4 -4
  4. data/exe/typeprof +5 -1
  5. data/lib/typeprof/analyzer.rb +261 -66
  6. data/lib/typeprof/arguments.rb +1 -0
  7. data/lib/typeprof/builtin.rb +30 -22
  8. data/lib/typeprof/cli.rb +22 -2
  9. data/lib/typeprof/code-range.rb +177 -0
  10. data/lib/typeprof/config.rb +43 -18
  11. data/lib/typeprof/container-type.rb +10 -1
  12. data/lib/typeprof/export.rb +199 -20
  13. data/lib/typeprof/import.rb +30 -4
  14. data/lib/typeprof/iseq.rb +504 -200
  15. data/lib/typeprof/lsp.rb +865 -0
  16. data/lib/typeprof/method.rb +17 -13
  17. data/lib/typeprof/type.rb +46 -38
  18. data/lib/typeprof/utils.rb +18 -1
  19. data/lib/typeprof/version.rb +1 -1
  20. data/lib/typeprof.rb +3 -0
  21. data/smoke/array15.rb +1 -1
  22. data/smoke/array6.rb +2 -2
  23. data/smoke/array8.rb +1 -1
  24. data/smoke/block-args2.rb +3 -3
  25. data/smoke/block-args3.rb +4 -4
  26. data/smoke/break2.rb +1 -1
  27. data/smoke/gvar2.rb +0 -3
  28. data/smoke/hash-bot.rb +1 -1
  29. data/smoke/hash4.rb +1 -1
  30. data/smoke/identifier_keywords.rb +17 -0
  31. data/smoke/next2.rb +1 -1
  32. data/smoke/or_raise.rb +18 -0
  33. data/smoke/parameterizedd-self.rb +2 -2
  34. data/smoke/pattern-match1.rb +1 -6
  35. data/smoke/rbs-vars.rb +0 -3
  36. data/testbed/ao.rb +1 -1
  37. data/typeprof-lsp +3 -0
  38. data/typeprof.gemspec +1 -1
  39. data/vscode/.gitignore +5 -0
  40. data/vscode/.vscode/launch.json +16 -0
  41. data/vscode/.vscodeignore +7 -0
  42. data/vscode/README.md +22 -0
  43. data/vscode/development.md +31 -0
  44. data/vscode/package-lock.json +2211 -0
  45. data/vscode/package.json +71 -0
  46. data/vscode/sandbox/test.rb +24 -0
  47. data/vscode/src/extension.ts +285 -0
  48. data/vscode/tsconfig.json +15 -0
  49. metadata +20 -5
@@ -26,7 +26,7 @@ module TypeProf
26
26
  end
27
27
 
28
28
  def show_message(terminated, output)
29
- if Config.options[:show_typeprof_version]
29
+ if Config.current.options[:show_typeprof_version]
30
30
  output.puts "# TypeProf #{ VERSION }"
31
31
  output.puts
32
32
  end
@@ -38,7 +38,7 @@ module TypeProf
38
38
 
39
39
  def show_error(errors, backward_edge, output)
40
40
  return if errors.empty?
41
- return unless Config.options[:show_errors]
41
+ return unless Config.current.options[:show_errors]
42
42
 
43
43
  output.puts "# Errors"
44
44
  errors.each do |ep, msg|
@@ -69,14 +69,17 @@ module TypeProf
69
69
  end
70
70
 
71
71
  def show_gvars(scratch, gvars, output)
72
+ gvars = gvars.dump.filter_map do |gvar_name, entry|
73
+ if entry.type != Type.bot && !entry.rbs_declared
74
+ [gvar_name, entry]
75
+ end
76
+ end
72
77
  # A signature for global variables is not supported in RBS
73
- return if gvars.dump.empty?
78
+ return if gvars.empty?
74
79
 
75
80
  output.puts "# Global variables"
76
- gvars.dump.each do |gvar_name, entry|
77
- next if entry.type == Type.bot
78
- s = entry.rbs_declared ? "#" : ""
79
- output.puts s + "#{ gvar_name }: #{ entry.type.screen_name(scratch) }"
81
+ gvars.each do |gvar_name, entry|
82
+ output.puts "#{ gvar_name }: #{ entry.type.screen_name(scratch) }"
80
83
  end
81
84
  output.puts
82
85
  end
@@ -107,16 +110,17 @@ module TypeProf
107
110
  @scratch.namespace = class_def.name
108
111
 
109
112
  consts = {}
110
- class_def.consts.each do |name, (ty, absolute_path)|
113
+ class_def.consts.each do |name, (ty, loc)|
114
+ next unless loc
111
115
  next if ty.is_a?(Type::Class)
112
- next if !absolute_path || Config.check_dir_filter(absolute_path) == :exclude
116
+ next if Config.current.check_dir_filter(loc[0]) == :exclude
113
117
  consts[name] = ty.screen_name(@scratch)
114
118
  end
115
119
 
116
120
  modules = class_def.modules.to_h do |kind, mods|
117
121
  mods = mods.to_h do |singleton, mods|
118
122
  mods = mods.filter_map do |mod_def, _type_args, absolute_paths|
119
- next if absolute_paths.all? {|path| !path || Config.check_dir_filter(path) == :exclude }
123
+ next if absolute_paths.all? {|path| !path || Config.current.check_dir_filter(path) == :exclude }
120
124
  Type::Instance.new(mod_def.klass_obj).screen_name(@scratch)
121
125
  end
122
126
  [singleton, mods]
@@ -139,7 +143,7 @@ module TypeProf
139
143
 
140
144
  ctx = ctxs.find {|ctx| ctx.mid == mid } || ctxs.first
141
145
 
142
- next if Config.check_dir_filter(ctx.iseq.absolute_path) == :exclude
146
+ next if Config.current.check_dir_filter(ctx.iseq.absolute_path) == :exclude
143
147
 
144
148
  method_name = mid
145
149
  method_name = "self.#{ method_name }" if singleton
@@ -149,7 +153,7 @@ module TypeProf
149
153
  source_locations[key] ||= ctx.iseq.source_location(0)
150
154
  (methods[key] ||= []) << @scratch.show_method_signature(ctx)
151
155
  when AliasMethodDef
152
- next if mdef.def_ep && Config.check_dir_filter(mdef.def_ep.source_location) == :exclude
156
+ next if mdef.def_ep && Config.current.check_dir_filter(mdef.def_ep.source_location) == :exclude
153
157
  alias_name, orig_name = mid, mdef.orig_mid
154
158
  if singleton
155
159
  alias_name = "self.#{ alias_name }"
@@ -162,7 +166,7 @@ module TypeProf
162
166
  when AttrMethodDef
163
167
  next if !mdef.def_ep
164
168
  absolute_path = mdef.def_ep.ctx.iseq.absolute_path
165
- next if !absolute_path || Config.check_dir_filter(absolute_path) == :exclude
169
+ next if !absolute_path || Config.current.check_dir_filter(absolute_path) == :exclude
166
170
  mid = mid.to_s[0..-2].to_sym if mid.to_s.end_with?("=")
167
171
  method_name = mid
168
172
  method_name = "self.#{ mid }" if singleton
@@ -185,13 +189,14 @@ module TypeProf
185
189
  key = [:rbs, method_name]
186
190
  methods[key] = sigs
187
191
  visibilities[key] ||= mdef.pub_meth
192
+ source_locations[key] ||= mdef.iseq&.source_location(0)
188
193
  end
189
194
  end
190
195
  end
191
196
  end
192
197
 
193
198
  ivars = ivars.map do |(singleton, var), entry|
194
- next if entry.absolute_paths.all? {|path| Config.check_dir_filter(path) == :exclude }
199
+ next if entry.absolute_paths.all? {|path| Config.current.check_dir_filter(path) == :exclude }
195
200
  ty = entry.type
196
201
  next unless var.to_s.start_with?("@")
197
202
  var = "self.#{ var }" if singleton
@@ -201,12 +206,12 @@ module TypeProf
201
206
  end.compact
202
207
 
203
208
  cvars = cvars.map do |var, entry|
204
- next if entry.absolute_paths.all? {|path| Config.check_dir_filter(path) == :exclude }
209
+ next if entry.absolute_paths.all? {|path| Config.current.check_dir_filter(path) == :exclude }
205
210
  next if entry.rbs_declared
206
211
  [var, entry.type.screen_name(@scratch)]
207
212
  end.compact
208
213
 
209
- if !class_def.absolute_path || Config.check_dir_filter(class_def.absolute_path) == :exclude
214
+ if !class_def.absolute_path || Config.current.check_dir_filter(class_def.absolute_path) == :exclude
210
215
  if methods.keys.all? {|type,| type == :rbs }
211
216
  return nil if consts.empty? && modules[:before][true].empty? && modules[:before][false].empty? && modules[:after][true].empty? && modules[:after][false].empty? && ivars.empty? && cvars.empty? && inner_classes.empty?
212
217
  end
@@ -229,8 +234,182 @@ module TypeProf
229
234
  )
230
235
  end
231
236
 
237
+ def conv_class_lsp(namespace, class_def)
238
+ @scratch.namespace = namespace
239
+
240
+ if class_def.klass_obj.superclass != :__root__ && class_def.klass_obj.superclass
241
+ omit = class_def.klass_obj.superclass == Type::Builtin[:obj] || class_def.klass_obj == Type::Builtin[:obj]
242
+ superclass = omit ? nil : @scratch.get_class_name(class_def.klass_obj.superclass)
243
+ type_args = class_def.klass_obj.superclass_type_args
244
+ if type_args && !type_args.empty?
245
+ superclass += "[#{ type_args.map {|ty| ty.screen_name(@scratch) }.join(", ") }]"
246
+ end
247
+ end
248
+
249
+ @scratch.namespace = class_def.name
250
+
251
+ consts = {}
252
+ class_def.consts.each do |name, (ty, loc)|
253
+ next unless loc
254
+ next if ty.is_a?(Type::Class)
255
+ next if Config.current.check_dir_filter(loc[0]) == :exclude
256
+ consts[name] = ty.screen_name(@scratch)
257
+ end
258
+
259
+ modules = class_def.modules.to_h do |kind, mods|
260
+ mods = mods.to_h do |singleton, mods|
261
+ mods = mods.filter_map do |mod_def, _type_args, absolute_paths|
262
+ next if absolute_paths.all? {|path| !path || Config.current.check_dir_filter(path) == :exclude }
263
+ Type::Instance.new(mod_def.klass_obj).screen_name(@scratch)
264
+ end
265
+ [singleton, mods]
266
+ end
267
+ [kind, mods]
268
+ end
269
+
270
+ visibilities = {}
271
+ source_locations = {}
272
+ methods = {}
273
+ ivars = class_def.ivars.dump
274
+ cvars = class_def.cvars.dump
275
+
276
+ class_def.methods.each do |(singleton, mid), mdefs|
277
+ mdefs.each do |mdef|
278
+ case mdef
279
+ when ISeqMethodDef
280
+ ctxs = @iseq_method_to_ctxs[mdef]
281
+ next unless ctxs
282
+
283
+ ctx = ctxs.find {|ctx| ctx.mid == mid } || ctxs.first
284
+
285
+ next if Config.current.check_dir_filter(ctx.iseq.absolute_path) == :exclude
286
+
287
+ method_name = mid
288
+ method_name = "self.#{ method_name }" if singleton
289
+
290
+ key = [:iseq, method_name]
291
+ visibilities[key] ||= mdef.pub_meth
292
+ source_locations[key] ||= [ctx.iseq.source_location(0)]
293
+ sig = @scratch.show_method_signature(ctx)
294
+ (methods[key] ||= []) << sig if sig
295
+ when AliasMethodDef
296
+ alias_name, orig_name = mid, mdef.orig_mid
297
+ if singleton
298
+ alias_name = "self.#{ alias_name }"
299
+ orig_name = "self.#{ orig_name }"
300
+ end
301
+ key = [:alias, alias_name]
302
+ visibilities[key] ||= mdef.pub_meth
303
+ source_locations[key] ||= [mdef.def_ep&.source_location]
304
+ methods[key] = orig_name
305
+ when AttrMethodDef
306
+ next if !mdef.def_ep
307
+ absolute_path = mdef.def_ep.ctx.iseq.absolute_path
308
+ next if !absolute_path || Config.current.check_dir_filter(absolute_path) == :exclude
309
+ mid = mid.to_s[0..-2].to_sym if mid.to_s.end_with?("=")
310
+ method_name = mid
311
+ method_name = "self.#{ mid }" if singleton
312
+ method_name = [method_name, :"@#{ mid }" != mdef.ivar]
313
+ key = [:attr, method_name]
314
+ visibilities[key] ||= mdef.pub_meth
315
+ source_locations[key] ||= [mdef.def_ep.source_location]
316
+ if methods[key]
317
+ if methods[key][0] != mdef.kind
318
+ methods[key][0] = :accessor
319
+ end
320
+ else
321
+ entry = ivars[[singleton, mdef.ivar]]
322
+ ty = entry ? entry.type : Type.any
323
+ methods[key] = [mdef.kind, ty.screen_name(@scratch), ty.include_untyped?(@scratch)]
324
+ end
325
+ when TypedMethodDef
326
+ if mdef.rbs_source
327
+ method_name, sigs, rbs_code_range = mdef.rbs_source
328
+ key = [:rbs, method_name]
329
+ methods[key] = sigs
330
+ visibilities[key] ||= mdef.pub_meth
331
+ source_locations[key] ||= [mdef.iseq&.source_location(0), rbs_code_range]
332
+ end
333
+ end
334
+ end
335
+ end
336
+
337
+ ivars = ivars.map do |(singleton, var), entry|
338
+ next if entry.absolute_paths.all? {|path| Config.current.check_dir_filter(path) == :exclude }
339
+ ty = entry.type
340
+ next unless var.to_s.start_with?("@")
341
+ var = "self.#{ var }" if singleton
342
+ next if methods[[:attr, [singleton ? "self.#{ var.to_s[1..] }" : var.to_s[1..].to_sym, false]]]
343
+ next if entry.rbs_declared
344
+ [var, ty.screen_name(@scratch)]
345
+ end.compact
346
+
347
+ cvars = cvars.map do |var, entry|
348
+ next if entry.absolute_paths.all? {|path| Config.current.check_dir_filter(path) == :exclude }
349
+ next if entry.rbs_declared
350
+ [var, entry.type.screen_name(@scratch)]
351
+ end.compact
352
+
353
+ if !class_def.absolute_path || Config.current.check_dir_filter(class_def.absolute_path) == :exclude
354
+ if methods.keys.all? {|type,| type == :rbs }
355
+ return nil if consts.empty? && modules[:before][true].empty? && modules[:before][false].empty? && modules[:after][true].empty? && modules[:after][false].empty? && ivars.empty? && cvars.empty?
356
+ end
357
+ end
358
+
359
+ @scratch.namespace = nil
360
+
361
+ ClassData.new(
362
+ kind: class_def.kind,
363
+ name: class_def.name,
364
+ superclass: superclass,
365
+ consts: consts,
366
+ modules: modules,
367
+ ivars: ivars,
368
+ cvars: cvars,
369
+ methods: methods,
370
+ visibilities: visibilities,
371
+ source_locations: source_locations,
372
+ )
373
+ end
374
+
232
375
  ClassData = Struct.new(:kind, :name, :superclass, :consts, :modules, :ivars, :cvars, :methods, :visibilities, :source_locations, :inner_classes, keyword_init: true)
233
376
 
377
+ def show_lsp
378
+ res = []
379
+ @class_defs.each_value do |class_def|
380
+ class_data = conv_class_lsp([], class_def)
381
+ next unless class_data
382
+ class_data.methods.each do |key, arg|
383
+ source_location, rbs_code_range = class_data.source_locations[key]
384
+ type, (method_name, hidden) = key
385
+ case type
386
+ when :attr
387
+ kind, ty, untyped = *arg
388
+ line = "attr_#{ kind } #{ method_name }#{ hidden ? "()" : "" }: #{ ty }"
389
+ when :rbs
390
+ sigs = arg.sort.join(" | ")
391
+ line = "# def #{ method_name }: #{ sigs }"
392
+ when :iseq
393
+ sigs = []
394
+ untyped = false
395
+ arg.each do |sig, untyped0|
396
+ sigs << sig
397
+ untyped ||= untyped0
398
+ end
399
+ sigs = sigs.sort.join(" | ")
400
+ line = "def #{ method_name }: #{ sigs }"
401
+ when :alias
402
+ orig_name = arg
403
+ line = "alias #{ method_name } #{ orig_name }"
404
+ end
405
+ if source_location =~ /:(\d+)$/
406
+ res << [$`, $1.to_i, line, rbs_code_range, class_data.kind, class_data.name]
407
+ end
408
+ end
409
+ end
410
+ res
411
+ end
412
+
234
413
  def show(stat_eps, output)
235
414
  # make the class hierarchy
236
415
  root = {}
@@ -267,7 +446,7 @@ module TypeProf
267
446
  coverage = {}
268
447
  stat_eps.each do |ep|
269
448
  path = ep.ctx.iseq.path
270
- lineno = ep.ctx.iseq.linenos[ep.pc] - 1
449
+ lineno = ep.ctx.iseq.insns[ep.pc].lineno - 1
271
450
  (coverage[path] ||= [])[lineno] ||= 0
272
451
  (coverage[path] ||= [])[lineno] += 1
273
452
  end
@@ -337,7 +516,7 @@ module TypeProf
337
516
  prev_vis = vis
338
517
  end
339
518
  source_location = class_data.source_locations[key]
340
- if Config.options[:show_source_locations] && source_location
519
+ if Config.current.options[:show_source_locations] && source_location
341
520
  lines << nil
342
521
  lines << (indent + " # #{ source_location }")
343
522
  end
@@ -345,7 +524,7 @@ module TypeProf
345
524
  case type
346
525
  when :attr
347
526
  kind, ty, untyped = *arg
348
- exclude = Config.options[:exclude_untyped] && untyped ? "#" : " " # XXX
527
+ exclude = Config.current.options[:exclude_untyped] && untyped ? "#" : " " # XXX
349
528
  lines << (indent + "#{ exclude } attr_#{ kind } #{ method_name }#{ hidden ? "()" : "" }: #{ ty }")
350
529
  when :rbs
351
530
  sigs = arg.sort.join("\n" + indent + "#" + " " * (method_name.size + 5) + "| ")
@@ -358,7 +537,7 @@ module TypeProf
358
537
  untyped ||= untyped0
359
538
  end
360
539
  sigs = sigs.sort.join("\n" + indent + " " * (method_name.size + 6) + "| ")
361
- exclude = Config.options[:exclude_untyped] && untyped ? "#" : " " # XXX
540
+ exclude = Config.current.options[:exclude_untyped] && untyped ? "#" : " " # XXX
362
541
  lines << (indent + "#{ exclude } def #{ method_name }: #{ sigs }")
363
542
  when :alias
364
543
  orig_name = arg
@@ -4,9 +4,14 @@ module TypeProf
4
4
  class RBSReader
5
5
  def initialize
6
6
  @repo = RBS::Repository.new
7
- Config.gem_repo_dirs.each do |dir|
7
+ Config.current.gem_repo_dirs.each do |dir|
8
8
  @repo.add(Pathname(dir))
9
9
  end
10
+ collection_path = Config.current.collection_path
11
+ if collection_path&.exist?
12
+ collection_lock = RBS::Collection::Config.lockfile_of(collection_path)
13
+ @repo.add(collection_lock.repo_path)
14
+ end
10
15
  @env, @builtin_env_json = RBSReader.get_builtin_env
11
16
  end
12
17
 
@@ -66,6 +71,14 @@ module TypeProf
66
71
  RBSReader.load_rbs(@env, new_decls)
67
72
  end
68
73
 
74
+ def load_rbs_collection(collection_path)
75
+ loader = RBS::EnvironmentLoader.new(core_root: nil)
76
+ collection_lock = RBS::Collection::Config.lockfile_of(collection_path)
77
+ loader.add_collection(collection_lock)
78
+ new_decls = loader.load(env: @env).map {|decl,| decl }
79
+ RBSReader.load_rbs(@env, new_decls)
80
+ end
81
+
69
82
  def self.load_rbs(env, new_decls)
70
83
  all_env = env.resolve_type_names
71
84
  resolver = RBS::TypeNameResolver.from_env(all_env)
@@ -157,7 +170,11 @@ module TypeProf
157
170
  end
158
171
 
159
172
  method_def = conv_method_def(method_types, visibility)
160
- rbs_source = [(member.kind == :singleton ? "self." : "") + member.name.to_s, member.types.map {|type| type.location.source }]
173
+ rbs_source = [
174
+ (member.kind == :singleton ? "self." : "") + member.name.to_s,
175
+ member.types.map {|type| type.location.source },
176
+ [member.location.name, CodeRange.from_rbs(member.location)],
177
+ ]
161
178
  if member.instance?
162
179
  methods[[false, name]] = method_def
163
180
  rbs_sources[[false, name]] = rbs_source
@@ -459,6 +476,8 @@ module TypeProf
459
476
  end
460
477
  when RBS::Types::Union
461
478
  [:union, ty.types.map {|ty2| conv_type(ty2) }.compact]
479
+ when RBS::Types::Intersection
480
+ [:intersection, ty.types.map {|ty2| conv_type(ty2) }.compact]
462
481
  when RBS::Types::Optional
463
482
  [:optional, conv_type(ty.type)]
464
483
  when RBS::Types::Interface
@@ -512,6 +531,10 @@ module TypeProf
512
531
  Import.new(scratch, scratch.rbs_reader.load_rbs_string(rbs_name, rbs_code)).import(true)
513
532
  end
514
533
 
534
+ def self.import_rbs_collection(scratch, collection_path)
535
+ Import.new(scratch, scratch.rbs_reader.load_rbs_collection(collection_path)).import(true)
536
+ end
537
+
515
538
  def initialize(scratch, json)
516
539
  @scratch = scratch
517
540
  @json = json
@@ -527,7 +550,7 @@ module TypeProf
527
550
  superclass = path_to_klass(superclass) if superclass
528
551
  base_klass = path_to_klass(classpath[0..-2])
529
552
 
530
- klass = @scratch.get_constant(base_klass, name)
553
+ klass, = @scratch.get_constant(base_klass, name)
531
554
  if klass.is_a?(Type::Any)
532
555
  klass = @scratch.new_class(base_klass, name, type_params, superclass, nil)
533
556
 
@@ -729,6 +752,9 @@ module TypeProf
729
752
  when :union
730
753
  tys = ty[1]
731
754
  Type::Union.create(Utils::Set[*tys.map {|ty2| conv_type(ty2) }], nil) # XXX: Array and Hash support
755
+ when :intersection
756
+ tys = ty[1]
757
+ conv_type(tys.first) # XXX: This is wrong! We need to support intersection type
732
758
  when :var
733
759
  Type::Var.new(ty[1])
734
760
  when :proc
@@ -743,7 +769,7 @@ module TypeProf
743
769
  def path_to_klass(path)
744
770
  klass = Type::Builtin[:obj]
745
771
  path.each do |name|
746
- klass = @scratch.get_constant(klass, name)
772
+ klass, = @scratch.get_constant(klass, name)
747
773
  if klass == Type.any
748
774
  raise TypeProfError.new("A constant `#{ path.join("::") }' is used but not defined in RBS")
749
775
  end