typeprof 0.31.1 → 0.33.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 (38) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -1
  3. data/doc/report_guide.md +88 -0
  4. data/lib/typeprof/cli/cli.rb +9 -3
  5. data/lib/typeprof/code_range.rb +7 -5
  6. data/lib/typeprof/core/ast/base.rb +31 -6
  7. data/lib/typeprof/core/ast/call.rb +120 -39
  8. data/lib/typeprof/core/ast/const.rb +22 -11
  9. data/lib/typeprof/core/ast/control.rb +62 -32
  10. data/lib/typeprof/core/ast/meta.rb +279 -14
  11. data/lib/typeprof/core/ast/method.rb +82 -24
  12. data/lib/typeprof/core/ast/misc.rb +41 -12
  13. data/lib/typeprof/core/ast/module.rb +38 -4
  14. data/lib/typeprof/core/ast/pattern.rb +51 -23
  15. data/lib/typeprof/core/ast/sig_decl.rb +141 -27
  16. data/lib/typeprof/core/ast/sig_type.rb +113 -32
  17. data/lib/typeprof/core/ast/value.rb +14 -6
  18. data/lib/typeprof/core/ast/variable.rb +17 -4
  19. data/lib/typeprof/core/ast.rb +98 -14
  20. data/lib/typeprof/core/builtin.rb +184 -12
  21. data/lib/typeprof/core/env/method.rb +343 -6
  22. data/lib/typeprof/core/env/method_entity.rb +18 -15
  23. data/lib/typeprof/core/env/module_entity.rb +116 -18
  24. data/lib/typeprof/core/env/static_read.rb +4 -4
  25. data/lib/typeprof/core/env/type_alias_entity.rb +1 -1
  26. data/lib/typeprof/core/env/value_entity.rb +25 -3
  27. data/lib/typeprof/core/env.rb +113 -23
  28. data/lib/typeprof/core/graph/box.rb +508 -63
  29. data/lib/typeprof/core/graph/change_set.rb +64 -46
  30. data/lib/typeprof/core/graph/filter.rb +8 -5
  31. data/lib/typeprof/core/graph/vertex.rb +20 -19
  32. data/lib/typeprof/core/service.rb +340 -24
  33. data/lib/typeprof/core/type.rb +41 -7
  34. data/lib/typeprof/core/util.rb +6 -0
  35. data/lib/typeprof/lsp/messages.rb +5 -0
  36. data/lib/typeprof/lsp/server.rb +35 -4
  37. data/lib/typeprof/version.rb +1 -1
  38. metadata +3 -2
@@ -7,7 +7,7 @@ module TypeProf::Core
7
7
  @rbs_text_nodes = {}
8
8
 
9
9
  @genv = GlobalEnv.new
10
- @genv.load_core_rbs(load_rbs_declarations(@options[:rbs_collection]).declarations)
10
+ @genv.load_core_rbs(load_rbs_declarations(@options[:rbs_collection]).declarations, @options[:position_encoding])
11
11
 
12
12
  Builtin.new(genv).deploy
13
13
  end
@@ -38,12 +38,12 @@ module TypeProf::Core
38
38
  end
39
39
 
40
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
- Dir.glob(File.expand_path(rbs_folder + "/**/*.{rb,rbs}")) do |path|
45
- update_file(path, nil)
46
- end
41
+ # Analyze RBS files first so that type declarations are available during RB type inference
42
+ all_files = [rb_folder, rbs_folder].flat_map { |folder| Dir.glob(File.expand_path(folder + "/**/*.{rb,rbs}")) }
43
+ rbs_files, rb_files = separate_rbs_and_rb(all_files.uniq)
44
+
45
+ rbs_files.each { |path| update_rbs_file(path, nil) }
46
+ rb_files.each { |path| update_rb_file(path, nil) }
47
47
  end
48
48
 
49
49
  def update_file(path, code)
@@ -58,7 +58,7 @@ module TypeProf::Core
58
58
  prev_node = @rb_text_nodes[path]
59
59
 
60
60
  code = File.read(path) unless code
61
- node = AST.parse_rb(path, code)
61
+ node = AST.parse_rb(path, code, @options[:position_encoding])
62
62
  return false unless node
63
63
 
64
64
  node.diff(@rb_text_nodes[path]) if prev_node
@@ -76,13 +76,13 @@ module TypeProf::Core
76
76
  if prev_node
77
77
  live_vtxs = []
78
78
  node.get_vertexes(live_vtxs)
79
- set = Set[]
79
+ set = Set.empty
80
80
  live_vtxs.uniq.each {|vtx| set << vtx }
81
81
  live_vtxs = set
82
82
 
83
83
  dead_vtxs = []
84
84
  prev_node.get_vertexes(dead_vtxs)
85
- set = Set[]
85
+ set = Set.empty
86
86
  dead_vtxs.uniq.each {|vtx| set << vtx }
87
87
  dead_vtxs = set
88
88
 
@@ -93,7 +93,7 @@ module TypeProf::Core
93
93
 
94
94
  global_vtxs = []
95
95
  @genv.get_vertexes(global_vtxs)
96
- set = Set[]
96
+ set = Set.empty
97
97
  global_vtxs.uniq.each {|vtx| set << vtx }
98
98
  global_vtxs = set
99
99
 
@@ -119,7 +119,7 @@ module TypeProf::Core
119
119
 
120
120
  code = File.read(path) unless code
121
121
  begin
122
- decls = AST.parse_rbs(path, code)
122
+ decls = AST.parse_rbs(path, code, @options[:position_encoding])
123
123
  rescue RBS::ParsingError
124
124
  return false
125
125
  end
@@ -203,11 +203,20 @@ module TypeProf::Core
203
203
  if node.ret
204
204
  ty_defs = []
205
205
  node.ret.types.map do |ty, _source|
206
- if ty.is_a?(Type::Instance)
207
- ty.mod.module_decls.each do |mdecl|
208
- # TODO
206
+ mod = case ty
207
+ when Type::Instance, Type::Singleton
208
+ ty.mod
209
+ else
210
+ base = ty.base_type(@genv)
211
+ base.mod if base.is_a?(Type::Instance)
212
+ end
213
+
214
+ if mod
215
+ mod.module_decls.each do |mdecl|
216
+ decl_path = mdecl.lenv.path
217
+ ty_defs << [decl_path, mdecl.code_range] if decl_path
209
218
  end
210
- ty.mod.module_defs.each do |mdef_node|
219
+ mod.module_defs.each do |mdef_node|
211
220
  ty_defs << [mdef_node.lenv.path, mdef_node.code_range]
212
221
  end
213
222
  end
@@ -396,7 +405,9 @@ module TypeProf::Core
396
405
  break
397
406
  end
398
407
  end
399
- yield mid, "#{ mod.cpath.join("::" )}#{ singleton ? "." : "#" }#{ mid } : #{ sig }" if sig
408
+ # The base class has no name to show
409
+ owner = mod.cpath.last == AST::StructNewNode::BASE_CNAME ? base_ty.mod : mod
410
+ yield mid, "#{ owner.cpath.join("::" )}#{ singleton ? "." : "#" }#{ mid } : #{ sig }" if sig
400
411
  end
401
412
  end
402
413
  end
@@ -405,15 +416,55 @@ module TypeProf::Core
405
416
  end
406
417
  end
407
418
 
419
+ def format_declared_const_path(cpath, stack)
420
+ scope_cpath =
421
+ stack.reverse_each.find do |entry|
422
+ (entry.is_a?(AST::ClassNode) || entry.is_a?(AST::ModuleNode)) &&
423
+ entry.static_cpath &&
424
+ !entry.static_cpath.empty?
425
+ end&.static_cpath
426
+
427
+ return cpath.join("::") unless scope_cpath
428
+ return cpath.join("::") unless cpath[0, scope_cpath.size] == scope_cpath
429
+
430
+ rel_cpath = cpath.drop(scope_cpath.size)
431
+ rel_cpath.empty? ? cpath.join("::") : rel_cpath.join("::")
432
+ end
433
+
434
+ def format_superclass_path(node, superclass)
435
+ sc_path = superclass.show_cpath
436
+
437
+ if node.is_a?(AST::ClassNode) && node.superclass_cpath
438
+ sc_node = node.superclass_cpath
439
+ if superclass_source_is_toplevel?(sc_node)
440
+ return "::#{ sc_path }"
441
+ end
442
+
443
+ if node.static_cpath && node.static_cpath.size > 1 &&
444
+ superclass.cpath == [node.static_cpath.last]
445
+ return "::#{ sc_path }"
446
+ end
447
+ end
448
+
449
+ sc_path
450
+ end
451
+
452
+ def superclass_source_is_toplevel?(const_node)
453
+ return false unless const_node.is_a?(AST::ConstantReadNode)
454
+ return true if const_node.toplevel
455
+ const_node.cbase ? superclass_source_is_toplevel?(const_node.cbase) : false
456
+ end
457
+
408
458
  def dump_declarations(path)
409
459
  stack = []
410
460
  out = []
461
+ seen_consts = Set.empty
411
462
  @rb_text_nodes[path]&.traverse do |event, node|
412
463
  case node
413
464
  when AST::ModuleNode
414
465
  if node.static_cpath
415
466
  if event == :enter
416
- out << " " * stack.size + "module #{ node.static_cpath.join("::") }"
467
+ out << " " * stack.size + "module #{ format_declared_const_path(node.static_cpath, stack) }"
417
468
  if stack == [:toplevel]
418
469
  out << "end"
419
470
  stack.pop
@@ -424,18 +475,22 @@ module TypeProf::Core
424
475
  out << " " * stack.size + "end"
425
476
  end
426
477
  end
427
- when AST::ClassNode, AST::SingletonClassNode
478
+ when AST::ClassNode, AST::SingletonClassNode, AST::StructNewNode
428
479
  if node.static_cpath
429
480
  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 }
430
481
 
431
482
  if event == :enter
432
- s = "class #{ node.static_cpath.join("::") }"
483
+ s = "class #{ format_declared_const_path(node.static_cpath, stack) }"
433
484
  mod = @genv.resolve_cpath(node.static_cpath)
434
485
  superclass = mod.superclass
486
+ # The base class is not shown; its members are printed inline
487
+ while superclass && superclass.cpath.last == AST::StructNewNode::BASE_CNAME
488
+ superclass = superclass.superclass
489
+ end
435
490
  if superclass == nil
436
491
  s << " # failed to identify its superclass"
437
492
  elsif superclass.cpath != []
438
- s << " < #{ superclass.show_cpath }"
493
+ s << " < #{ format_superclass_path(node, superclass) }"
439
494
  end
440
495
  if stack == [:toplevel]
441
496
  out << "end"
@@ -448,6 +503,18 @@ module TypeProf::Core
448
503
  out << " " * stack.size + "include #{ inc_mod.show_cpath }"
449
504
  end
450
505
  end
506
+ mod.extended_modules.each do |ext_def, ext_mod|
507
+ if ext_def.is_a?(AST::ConstantReadNode) && ext_def.lenv.path == path
508
+ out << " " * stack.size + "extend #{ ext_mod.show_cpath }"
509
+ end
510
+ end
511
+ # Output method definitions from meta nodes (StructNewNode etc.)
512
+ node.boxes(:mdef) do |mdef|
513
+ # A user-written method overrides the generated one
514
+ next if node.is_a?(AST::StructNewNode) &&
515
+ !@genv.resolve_method(node.static_cpath, mdef.singleton, mdef.mid).defs.empty?
516
+ out << " " * stack.size + "def #{ mdef.singleton ? "self." : "" }#{ mdef.mid }: " + mdef.show(@options[:output_parameter_names])
517
+ end
451
518
  else
452
519
  stack.pop
453
520
  out << " " * stack.size + "end"
@@ -456,7 +523,14 @@ module TypeProf::Core
456
523
  when AST::ConstantWriteNode
457
524
  if node.static_cpath
458
525
  if event == :enter
459
- out << " " * stack.size + "#{ node.static_cpath.join("::") }: #{ node.ret.show }"
526
+ unless seen_consts.include?(node.static_cpath)
527
+ seen_consts << node.static_cpath
528
+ cdef = @genv.resolve_const(node.static_cpath)
529
+ # Use the assigned value for a single write so a same-named class
530
+ # or module (e.g. declared in RBS) is not mixed in; union the types otherwise.
531
+ ret = cdef.defs.size > 1 ? cdef.vtx : node.ret
532
+ out << " " * stack.size + "#{ format_declared_const_path(node.static_cpath, stack) }: #{ ret.show }"
533
+ end
460
534
  end
461
535
  end
462
536
  else
@@ -496,10 +570,14 @@ module TypeProf::Core
496
570
  output.puts
497
571
  end
498
572
 
573
+ # Analyze RBS files first so that type declarations are available during RB type inference
574
+ rbs_files, rb_files = separate_rbs_and_rb(files)
575
+ sorted_files = rbs_files + rb_files
576
+
499
577
  i = 0
500
- show_files = files.select do |file|
578
+ show_files = sorted_files.select do |file|
501
579
  if @options[:display_indicator]
502
- $stderr << "\r[%d/%d] %s\e[K" % [i, files.size, file]
580
+ $stderr << "\r[%d/%d] %s\e[K" % [i, sorted_files.size, file]
503
581
  i += 1
504
582
  end
505
583
 
@@ -532,6 +610,244 @@ module TypeProf::Core
532
610
  end
533
611
  output.puts dump_declarations(file)
534
612
  end
613
+
614
+ if @options[:output_stats]
615
+ rb_files = show_files.reject {|f| File.extname(f) == ".rbs" }
616
+ stats = collect_stats(rb_files)
617
+ output.puts
618
+ output.puts format_stats(stats)
619
+ end
620
+ end
621
+
622
+ def collect_stats(files)
623
+ file_stats = []
624
+
625
+ files.each do |path|
626
+ methods = []
627
+ constants = []
628
+ seen_ivars = Set.empty
629
+ ivars = []
630
+ seen_cvars = Set.empty
631
+ cvars = []
632
+ seen_gvars = Set.empty
633
+ gvars = []
634
+
635
+ @rb_text_nodes[path]&.traverse do |event, node|
636
+ next unless event == :enter
637
+
638
+ node.boxes(:mdef) do |mdef|
639
+ param_slots = []
640
+ f = mdef.f_args
641
+ [f.req_positionals, f.opt_positionals, f.post_positionals, f.req_keywords, f.opt_keywords].each do |ary|
642
+ ary.each {|vtx| param_slots << classify_vertex(vtx) }
643
+ end
644
+ [f.rest_positionals, f.rest_keywords].each do |vtx|
645
+ param_slots << classify_vertex(vtx) if vtx
646
+ end
647
+
648
+ is_initialize = mdef.mid == :initialize
649
+ ret_slots = is_initialize ? [] : [classify_vertex(mdef.ret)]
650
+
651
+ blk = mdef.record_block
652
+ block_param_slots = []
653
+ block_ret_slots = []
654
+ if blk.used
655
+ blk.f_args.each {|vtx| block_param_slots << classify_vertex(vtx) }
656
+ block_ret_slots << classify_vertex(blk.ret)
657
+ end
658
+
659
+ methods << {
660
+ mid: mdef.mid,
661
+ singleton: mdef.singleton,
662
+ param_slots: param_slots,
663
+ ret_slots: ret_slots,
664
+ block_param_slots: block_param_slots,
665
+ block_ret_slots: block_ret_slots,
666
+ }
667
+ end
668
+
669
+ if node.is_a?(AST::ConstantWriteNode) && node.static_cpath
670
+ constants << classify_vertex(node.ret)
671
+ end
672
+
673
+ if node.is_a?(AST::InstanceVariableWriteNode)
674
+ scope = node.lenv.cref.scope_level
675
+ if scope == :class || scope == :instance
676
+ key = [node.lenv.cref.cpath, scope == :class, node.var]
677
+ unless seen_ivars.include?(key)
678
+ seen_ivars << key
679
+ ve = @genv.resolve_ivar(key[0], key[1], key[2])
680
+ ivars << classify_vertex(ve.vtx)
681
+ end
682
+ end
683
+ end
684
+
685
+ if node.is_a?(AST::ClassVariableWriteNode)
686
+ key = [node.lenv.cref.cpath, node.var]
687
+ unless seen_cvars.include?(key)
688
+ seen_cvars << key
689
+ ve = @genv.resolve_cvar(key[0], key[1])
690
+ cvars << classify_vertex(ve.vtx)
691
+ end
692
+ end
693
+
694
+ if node.is_a?(AST::GlobalVariableWriteNode)
695
+ unless seen_gvars.include?(node.var)
696
+ seen_gvars << node.var
697
+ ve = @genv.resolve_gvar(node.var)
698
+ gvars << classify_vertex(ve.vtx)
699
+ end
700
+ end
701
+ end
702
+
703
+ file_stats << {
704
+ path: path,
705
+ methods: methods,
706
+ constants: constants,
707
+ ivars: ivars,
708
+ cvars: cvars,
709
+ gvars: gvars,
710
+ }
711
+ end
712
+
713
+ file_stats
714
+ end
715
+
716
+ def classify_vertex(vtx)
717
+ vtx.types.empty? ? :untyped : :typed
718
+ end
719
+
720
+ def format_stats(stats)
721
+ total_methods = 0
722
+ fully_typed = 0
723
+ partially_typed = 0
724
+ fully_untyped = 0
725
+
726
+ slot_categories = %i[param ret blk_param blk_ret const ivar cvar gvar]
727
+ typed = Hash.new(0)
728
+ untyped = Hash.new(0)
729
+
730
+ file_summaries = []
731
+
732
+ stats.each do |file|
733
+ f_typed = 0
734
+ f_total = 0
735
+
736
+ file[:methods].each do |m|
737
+ total_methods += 1
738
+
739
+ method_slot_keys = %i[param_slots ret_slots block_param_slots block_ret_slots]
740
+ category_keys = %i[param ret blk_param blk_ret]
741
+
742
+ all_slots = method_slot_keys.flat_map {|k| m[k] }
743
+
744
+ method_slot_keys.zip(category_keys) do |slot_key, cat|
745
+ m[slot_key].each do |s|
746
+ if s == :typed
747
+ typed[cat] += 1
748
+ else
749
+ untyped[cat] += 1
750
+ end
751
+ end
752
+ end
753
+
754
+ if all_slots.empty? || all_slots.all? {|s| s == :typed }
755
+ fully_typed += 1
756
+ elsif all_slots.none? {|s| s == :typed }
757
+ fully_untyped += 1
758
+ else
759
+ partially_typed += 1
760
+ end
761
+
762
+ f_typed += all_slots.count(:typed)
763
+ f_total += all_slots.size
764
+ end
765
+
766
+ %i[constants ivars cvars gvars].zip(%i[const ivar cvar gvar]) do |data_key, cat|
767
+ file[data_key].each do |s|
768
+ f_total += 1
769
+ if s == :typed
770
+ typed[cat] += 1
771
+ f_typed += 1
772
+ else
773
+ untyped[cat] += 1
774
+ end
775
+ end
776
+ end
777
+
778
+ if f_total > 0
779
+ file_summaries << {
780
+ path: file[:path],
781
+ methods: file[:methods].size,
782
+ typed: f_typed,
783
+ total: f_total,
784
+ }
785
+ end
786
+ end
787
+
788
+ overall_typed = slot_categories.sum {|c| typed[c] }
789
+ overall_untyped = slot_categories.sum {|c| untyped[c] }
790
+ overall_total = overall_typed + overall_untyped
791
+
792
+ labels = {
793
+ param: "Parameter slots",
794
+ ret: "Return slots",
795
+ blk_param: "Block parameter slots",
796
+ blk_ret: "Block return slots",
797
+ const: "Constants",
798
+ ivar: "Instance variables",
799
+ cvar: "Class variables",
800
+ gvar: "Global variables",
801
+ }
802
+
803
+ lines = []
804
+ lines << "# TypeProf Evaluation Statistics"
805
+ lines << "#"
806
+ lines << "# Total methods: #{ total_methods }"
807
+ lines << "# Fully typed: #{ fully_typed }"
808
+ lines << "# Partially typed: #{ partially_typed }"
809
+ lines << "# Fully untyped: #{ fully_untyped }"
810
+
811
+ slot_categories.each do |cat|
812
+ total = typed[cat] + untyped[cat]
813
+ lines << "#"
814
+ lines << "# #{ labels[cat] }: #{ total }"
815
+ lines << "# Typed: #{ typed[cat] } (#{ pct(typed[cat], total) })"
816
+ lines << "# Untyped: #{ untyped[cat] } (#{ pct(untyped[cat], total) })"
817
+ end
818
+
819
+ lines << "#"
820
+ lines << "# Overall: #{ overall_typed }/#{ overall_total } typed (#{ pct(overall_typed, overall_total) })"
821
+ lines << "# #{ overall_untyped }/#{ overall_total } untyped (#{ pct(overall_untyped, overall_total) })"
822
+
823
+ if file_summaries.size > 1
824
+ lines << "#"
825
+ lines << "# Per-file breakdown:"
826
+ file_summaries.each do |fs|
827
+ lines << "# #{ fs[:path] }: #{ fs[:methods] } methods, #{ fs[:typed] }/#{ fs[:total] } typed (#{ pct(fs[:typed], fs[:total]) })"
828
+ end
829
+ end
830
+
831
+ lines.join("\n")
832
+ end
833
+
834
+ def pct(n, total)
835
+ return "0.0%" if total == 0
836
+ "#{ (n * 100.0 / total).round(1) }%"
837
+ end
838
+
839
+ private
840
+
841
+ def separate_rbs_and_rb(files)
842
+ files
843
+ .reject { |file| exclude_files.include?(File.expand_path(file)) }
844
+ .partition { |file| File.extname(file) == ".rbs" }
845
+ end
846
+
847
+ def exclude_files
848
+ @exclude_files ||= (@options[:exclude_patterns] || []).each_with_object(::Set.new) { |pattern, set|
849
+ Dir.glob(File.expand_path(pattern)) { |path| set << path }
850
+ }
535
851
  end
536
852
  end
537
853
  end
@@ -16,16 +16,33 @@ module TypeProf::Core
16
16
  end
17
17
 
18
18
  def self.extract_hash_value_type(s)
19
- if s.start_with?("Hash[") && s.end_with?("]")
19
+ begin
20
20
  type = RBS::Parser.parse_type(s)
21
+ collect_hash_value_types(type).uniq.join(" | ")
22
+ rescue
23
+ s
24
+ end
25
+ end
21
26
 
22
- if type.is_a?(RBS::Types::Union)
23
- type.types.map {|t| t.args[1].to_s }.join(" | ")
27
+ # Returns an array of value type strings from hash-like types
28
+ def self.collect_hash_value_types(type)
29
+ case type
30
+ when RBS::Types::Record
31
+ # Extract value types from record fields
32
+ # all_fields returns { key => [type, required] }
33
+ type.all_fields.values.map { |t, _required| t.to_s }
34
+ when RBS::Types::ClassInstance
35
+ # Handle Hash[K, V]
36
+ if type.name.name == :Hash && type.args.size == 2
37
+ [type.args[1].to_s]
24
38
  else
25
- type.args[1].to_s
39
+ [type.to_s]
26
40
  end
41
+ when RBS::Types::Union
42
+ # Handle union of types - extract value types from all hash-like types
43
+ type.types.flat_map { |t| collect_hash_value_types(t) }
27
44
  else
28
- s
45
+ [type.to_s]
29
46
  end
30
47
  end
31
48
 
@@ -60,7 +77,7 @@ module TypeProf::Core
60
77
 
61
78
  def get_instance_type(genv)
62
79
  params = @mod.type_params
63
- Instance.new(genv, @mod, params ? params.map { Source.new } : [])
80
+ Instance.new(genv, @mod, params ? params.map { Source.new } : []) # TODO: respect param_default_types
64
81
  end
65
82
  end
66
83
 
@@ -188,7 +205,24 @@ module TypeProf::Core
188
205
  end
189
206
 
190
207
  def show
191
- "<Proc>"
208
+ "Proc"
209
+ end
210
+ end
211
+
212
+ class Method < Type
213
+ def initialize(genv, recv_ty, mid)
214
+ @recv_ty = recv_ty
215
+ @mid = mid
216
+ end
217
+
218
+ attr_reader :recv_ty, :mid
219
+
220
+ def base_type(genv)
221
+ genv.method_type
222
+ end
223
+
224
+ def show
225
+ "Method"
192
226
  end
193
227
  end
194
228
 
@@ -6,6 +6,10 @@ module TypeProf::Core
6
6
  new(h)
7
7
  end
8
8
 
9
+ def self.empty
10
+ new(Hash.new(false))
11
+ end
12
+
9
13
  def initialize(hash)
10
14
  @hash = hash
11
15
  end
@@ -77,5 +81,7 @@ module TypeProf::Core
77
81
  end
78
82
  q.text "]"
79
83
  end
84
+
85
+ EMPTY = empty.freeze
80
86
  end
81
87
  end
@@ -56,6 +56,10 @@ module TypeProf::LSP
56
56
  class Message::Initialize < Message
57
57
  METHOD = "initialize" # request (required)
58
58
  def run
59
+ # Must negotiate encoding before add_workspaces so newly created Services honor it.
60
+ client_encodings = @params.dig(:capabilities, :general, :positionEncodings)
61
+ @server.negotiate_position_encoding(client_encodings)
62
+
59
63
  folders = @params[:workspaceFolders].map do |folder|
60
64
  folder => { uri:, }
61
65
  @server.uri_to_path(uri)
@@ -65,6 +69,7 @@ module TypeProf::LSP
65
69
 
66
70
  respond(
67
71
  capabilities: {
72
+ positionEncoding: @server.lsp_position_encoding,
68
73
  textDocumentSync: {
69
74
  openClose: true,
70
75
  change: 2, # Incremental
@@ -21,8 +21,8 @@ module TypeProf::LSP
21
21
  new(core_options, reader, writer).run
22
22
  end
23
23
 
24
- def self.start_socket(core_options)
25
- Socket.tcp_server_sockets("localhost", nil) do |servs|
24
+ def self.start_socket(core_options, port = 0)
25
+ Socket.tcp_server_sockets("localhost", port) do |servs|
26
26
  serv = servs[0].local_address
27
27
  $stdout << JSON.generate({
28
28
  host: serv.ip_address,
@@ -47,6 +47,15 @@ module TypeProf::LSP
47
47
  end
48
48
  end
49
49
 
50
+ # see: https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocuments
51
+ LSP_POSITION_ENCODINGS = {
52
+ Encoding::UTF_8 => "utf-8",
53
+ Encoding::UTF_16LE => "utf-16",
54
+ Encoding::UTF_32LE => "utf-32",
55
+ }.freeze
56
+
57
+ private_constant :LSP_POSITION_ENCODINGS
58
+
50
59
  def initialize(core_options, reader, writer, url_schema: nil)
51
60
  @core_options = core_options
52
61
  @cores = {}
@@ -62,9 +71,29 @@ module TypeProf::LSP
62
71
  @diagnostic_severity = :error
63
72
  end
64
73
 
65
- attr_reader :open_texts
74
+ attr_reader :open_texts, :position_encoding
66
75
  attr_accessor :signature_enabled
67
76
 
77
+ # Pick the first mutually-supported encoding from the client's preference-ordered list
78
+ # and store it. Falls back to UTF-16LE (mandatory per LSP 3.17 spec).
79
+ def negotiate_position_encoding(client_encodings)
80
+ @position_encoding = pick_position_encoding(client_encodings)
81
+ end
82
+
83
+ def lsp_position_encoding
84
+ LSP_POSITION_ENCODINGS.fetch(@position_encoding)
85
+ end
86
+
87
+ def pick_position_encoding(client_encodings)
88
+ return Encoding::UTF_16LE unless client_encodings.is_a?(Array)
89
+ client_encodings.each do |enc|
90
+ encoding = LSP_POSITION_ENCODINGS.key(enc)
91
+ return encoding if encoding
92
+ end
93
+ Encoding::UTF_16LE
94
+ end
95
+ private :pick_position_encoding
96
+
68
97
  #: (String) -> String
69
98
  def path_to_uri(path)
70
99
  @url_schema + File.expand_path(path).split("/").map {|s| CGI.escapeURIComponent(s) }.join("/")
@@ -104,9 +133,11 @@ module TypeProf::LSP
104
133
  puts "unknown severity: #{ severity }"
105
134
  end
106
135
  end
136
+ @core_options[:exclude_patterns] = conf[:exclude] if conf[:exclude]
137
+ service_options = @core_options.merge(position_encoding: @position_encoding)
107
138
  conf[:analysis_unit_dirs].each do |dir|
108
139
  dir = File.expand_path(dir, path)
109
- core = @cores[dir] = TypeProf::Core::Service.new(@core_options)
140
+ core = @cores[dir] = TypeProf::Core::Service.new(service_options)
110
141
  core.add_workspace(dir, @rbs_dir)
111
142
  end
112
143
  else
@@ -1,3 +1,3 @@
1
1
  module TypeProf
2
- VERSION = "0.31.1"
2
+ VERSION = "0.33.0"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: typeprof
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.31.1
4
+ version: 0.33.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yusuke Endoh
@@ -53,6 +53,7 @@ files:
53
53
  - bin/typeprof
54
54
  - doc/doc.ja.md
55
55
  - doc/doc.md
56
+ - doc/report_guide.md
56
57
  - lib/typeprof.rb
57
58
  - lib/typeprof/cli.rb
58
59
  - lib/typeprof/cli/cli.rb
@@ -117,7 +118,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
117
118
  - !ruby/object:Gem::Version
118
119
  version: '0'
119
120
  requirements: []
120
- rubygems_version: 3.6.9
121
+ rubygems_version: 4.0.16
121
122
  specification_version: 4
122
123
  summary: TypeProf is a type analysis tool for Ruby code based on abstract interpretation
123
124
  test_files: []