rigortype 0.1.4 → 0.1.6

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 (107) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +69 -56
  3. data/lib/rigor/analysis/buffer_binding.rb +36 -0
  4. data/lib/rigor/analysis/check_rules.rb +11 -1
  5. data/lib/rigor/analysis/dependency_source_inference/index.rb +14 -1
  6. data/lib/rigor/analysis/dependency_source_inference/return_type_heuristic.rb +105 -0
  7. data/lib/rigor/analysis/dependency_source_inference/walker.rb +32 -12
  8. data/lib/rigor/analysis/fact_store.rb +15 -3
  9. data/lib/rigor/analysis/project_scan.rb +39 -0
  10. data/lib/rigor/analysis/result.rb +11 -3
  11. data/lib/rigor/analysis/run_stats.rb +193 -0
  12. data/lib/rigor/analysis/runner.rb +681 -19
  13. data/lib/rigor/analysis/worker_session.rb +339 -0
  14. data/lib/rigor/builtins/hkt_builtins.rb +342 -0
  15. data/lib/rigor/builtins/imported_refinements.rb +6 -2
  16. data/lib/rigor/builtins/regex_refinement.rb +17 -12
  17. data/lib/rigor/builtins/static_return_refinements.rb +120 -0
  18. data/lib/rigor/cache/rbs_descriptor.rb +3 -1
  19. data/lib/rigor/cache/store.rb +72 -9
  20. data/lib/rigor/cli/lsp_command.rb +129 -0
  21. data/lib/rigor/cli/type_of_command.rb +44 -5
  22. data/lib/rigor/cli.rb +122 -10
  23. data/lib/rigor/configuration.rb +168 -7
  24. data/lib/rigor/environment/bundle_sig_discovery.rb +198 -0
  25. data/lib/rigor/environment/class_registry.rb +12 -3
  26. data/lib/rigor/environment/hkt_registry_holder.rb +33 -0
  27. data/lib/rigor/environment/lockfile_resolver.rb +125 -0
  28. data/lib/rigor/environment/rbs_collection_discovery.rb +126 -0
  29. data/lib/rigor/environment/rbs_coverage_report.rb +112 -0
  30. data/lib/rigor/environment/rbs_loader.rb +238 -7
  31. data/lib/rigor/environment/reflection.rb +152 -0
  32. data/lib/rigor/environment/reporters.rb +40 -0
  33. data/lib/rigor/environment.rb +179 -10
  34. data/lib/rigor/inference/acceptance.rb +83 -4
  35. data/lib/rigor/inference/builtins/method_catalog.rb +12 -5
  36. data/lib/rigor/inference/builtins/numeric_catalog.rb +15 -4
  37. data/lib/rigor/inference/expression_typer.rb +59 -2
  38. data/lib/rigor/inference/hkt_body.rb +171 -0
  39. data/lib/rigor/inference/hkt_body_parser.rb +363 -0
  40. data/lib/rigor/inference/hkt_reducer.rb +256 -0
  41. data/lib/rigor/inference/hkt_registry.rb +223 -0
  42. data/lib/rigor/inference/macro_block_self_type.rb +96 -0
  43. data/lib/rigor/inference/method_dispatcher/constant_folding.rb +29 -29
  44. data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +4 -4
  45. data/lib/rigor/inference/method_dispatcher/method_folding.rb +18 -1
  46. data/lib/rigor/inference/method_dispatcher/overload_selector.rb +126 -31
  47. data/lib/rigor/inference/method_dispatcher/receiver_affinity.rb +87 -0
  48. data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +46 -40
  49. data/lib/rigor/inference/method_dispatcher.rb +282 -6
  50. data/lib/rigor/inference/method_parameter_binder.rb +21 -11
  51. data/lib/rigor/inference/narrowing.rb +127 -8
  52. data/lib/rigor/inference/project_patched_methods.rb +70 -0
  53. data/lib/rigor/inference/project_patched_scanner.rb +210 -0
  54. data/lib/rigor/inference/scope_indexer.rb +156 -12
  55. data/lib/rigor/inference/statement_evaluator.rb +106 -6
  56. data/lib/rigor/inference/synthetic_method.rb +86 -0
  57. data/lib/rigor/inference/synthetic_method_index.rb +82 -0
  58. data/lib/rigor/inference/synthetic_method_scanner.rb +599 -0
  59. data/lib/rigor/language_server/buffer_table.rb +63 -0
  60. data/lib/rigor/language_server/completion_provider.rb +438 -0
  61. data/lib/rigor/language_server/debouncer.rb +86 -0
  62. data/lib/rigor/language_server/diagnostic_publisher.rb +167 -0
  63. data/lib/rigor/language_server/document_symbol_provider.rb +142 -0
  64. data/lib/rigor/language_server/folding_range_provider.rb +75 -0
  65. data/lib/rigor/language_server/hover_provider.rb +74 -0
  66. data/lib/rigor/language_server/hover_renderer.rb +312 -0
  67. data/lib/rigor/language_server/loop.rb +71 -0
  68. data/lib/rigor/language_server/project_context.rb +145 -0
  69. data/lib/rigor/language_server/selection_range_provider.rb +93 -0
  70. data/lib/rigor/language_server/server.rb +384 -0
  71. data/lib/rigor/language_server/signature_help_provider.rb +249 -0
  72. data/lib/rigor/language_server/synchronized_writer.rb +28 -0
  73. data/lib/rigor/language_server/uri.rb +40 -0
  74. data/lib/rigor/language_server.rb +29 -0
  75. data/lib/rigor/plugin/base.rb +63 -0
  76. data/lib/rigor/plugin/blueprint.rb +60 -0
  77. data/lib/rigor/plugin/loader.rb +3 -1
  78. data/lib/rigor/plugin/macro/block_as_method.rb +131 -0
  79. data/lib/rigor/plugin/macro/external_file.rb +143 -0
  80. data/lib/rigor/plugin/macro/heredoc_template.rb +315 -0
  81. data/lib/rigor/plugin/macro/trait_registry.rb +198 -0
  82. data/lib/rigor/plugin/macro.rb +31 -0
  83. data/lib/rigor/plugin/manifest.rb +127 -9
  84. data/lib/rigor/plugin/registry.rb +51 -2
  85. data/lib/rigor/plugin.rb +1 -0
  86. data/lib/rigor/rbs_extended/hkt_directives.rb +326 -0
  87. data/lib/rigor/rbs_extended.rb +82 -2
  88. data/lib/rigor/sig_gen/generator.rb +12 -3
  89. data/lib/rigor/trinary.rb +15 -11
  90. data/lib/rigor/type/app.rb +107 -0
  91. data/lib/rigor/type/bot.rb +6 -3
  92. data/lib/rigor/type/combinator.rb +12 -1
  93. data/lib/rigor/type/integer_range.rb +7 -7
  94. data/lib/rigor/type/refined.rb +18 -12
  95. data/lib/rigor/type/top.rb +4 -3
  96. data/lib/rigor/type.rb +1 -0
  97. data/lib/rigor/type_node/generic.rb +7 -1
  98. data/lib/rigor/type_node/identifier.rb +9 -1
  99. data/lib/rigor/type_node/string_literal.rb +4 -1
  100. data/lib/rigor/version.rb +1 -1
  101. data/sig/rigor/environment.rbs +11 -4
  102. data/sig/rigor/inference.rbs +2 -0
  103. data/sig/rigor/plugin/blueprint.rbs +7 -0
  104. data/sig/rigor/plugin/manifest.rbs +1 -1
  105. data/sig/rigor/plugin/registry.rbs +14 -1
  106. data/sig/rigor.rbs +37 -2
  107. metadata +92 -1
@@ -0,0 +1,193 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rbconfig"
4
+
5
+ module Rigor
6
+ module Analysis
7
+ # End-of-run telemetry for the `rigor check` CLI's `--stats`
8
+ # output. Captures four cheap-to-measure groups:
9
+ #
10
+ # - **Check targets** — the Ruby files the analyser actually
11
+ # walks for diagnostics (`expand_paths` output).
12
+ # - **Type universe** — RBS class/module declarations the
13
+ # analyser had visibility of, broken down by source:
14
+ # `project_sig` (declarations whose source file lives under
15
+ # the configured `signature_paths`) vs `bundled` (RBS core,
16
+ # stdlib libraries, gem-bundled RBS — everything outside
17
+ # the project's own `sig/` tree).
18
+ # - **Gem source-walk** — the ADR-10
19
+ # `dependencies.source_inference` catalogue. Reports the
20
+ # class count and the number of opt-in gems contributing.
21
+ # - **Process** — wall-clock seconds + peak resident set size.
22
+ #
23
+ # The split between "check targets" and "type universe" makes
24
+ # explicit that the analyser's diagnostic surface is bounded
25
+ # by the user-controlled `paths:` configuration; the (typically
26
+ # much larger) RBS class universe is symbol-discovery, not a
27
+ # diagnostic surface.
28
+ #
29
+ # Stats collection is intentionally cheap: wall + RSS are
30
+ # single syscalls, target file count is already in
31
+ # `expand_paths`, gem source-walk uses
32
+ # `Index#class_to_gem.size`, and the RBS class breakdown
33
+ # walks `class_decl_paths` (a frozen `Hash<String, String>`
34
+ # populated once per environment by the RBS loader; ~1000-2000
35
+ # entries × one `String#start_with?`).
36
+ class RunStats
37
+ attr_reader :wall_seconds, :peak_rss_bytes,
38
+ :target_files,
39
+ :rbs_classes_total, :rbs_classes_project_sig, :rbs_classes_bundled,
40
+ :gem_walk_classes, :gem_walk_gems, :rbs_attribution_available
41
+
42
+ def initialize(wall_seconds:, peak_rss_bytes:, # rubocop:disable Metrics/ParameterLists
43
+ target_files:,
44
+ rbs_classes_total:, rbs_classes_project_sig:, rbs_classes_bundled:,
45
+ gem_walk_classes:, gem_walk_gems:,
46
+ rbs_attribution_available: true)
47
+ @wall_seconds = wall_seconds
48
+ @peak_rss_bytes = peak_rss_bytes
49
+ @target_files = target_files
50
+ @rbs_classes_total = rbs_classes_total
51
+ @rbs_classes_project_sig = rbs_classes_project_sig
52
+ @rbs_classes_bundled = rbs_classes_bundled
53
+ @gem_walk_classes = gem_walk_classes
54
+ @gem_walk_gems = gem_walk_gems
55
+ @rbs_attribution_available = rbs_attribution_available
56
+ freeze
57
+ end
58
+
59
+ # Reports the process's resident set size in bytes. Source
60
+ # ordering: `/proc/self/status` (Linux — reads `VmHWM:`,
61
+ # the peak RSS the kernel records) first; otherwise
62
+ # `ps -o rss= -p <pid>` (macOS / BSD — reports CURRENT
63
+ # RSS, the closest universally-available proxy). Returns
64
+ # nil when neither route works so the formatter can render
65
+ # `unavailable` instead of misleading zero.
66
+ def self.peak_rss_bytes
67
+ from_proc = read_vmhwm_from_proc
68
+ return from_proc unless from_proc.nil?
69
+
70
+ from_ps = read_rss_via_ps
71
+ return from_ps unless from_ps.nil?
72
+
73
+ nil
74
+ end
75
+
76
+ def self.read_vmhwm_from_proc
77
+ return nil unless File.readable?("/proc/self/status")
78
+
79
+ File.foreach("/proc/self/status") do |line|
80
+ next unless line.start_with?("VmHWM:")
81
+
82
+ kb_token = line.split.find { |token| token.match?(/\A\d+\z/) }
83
+ return Integer(kb_token) * 1024 if kb_token
84
+ end
85
+ nil
86
+ rescue StandardError
87
+ nil
88
+ end
89
+
90
+ def self.read_rss_via_ps
91
+ out = `ps -o rss= -p #{Process.pid} 2>/dev/null`.strip
92
+ return nil if out.empty?
93
+
94
+ Integer(out) * 1024
95
+ rescue StandardError
96
+ nil
97
+ end
98
+
99
+ # Source-attribution sentinel produced by `RBS::Environment`
100
+ # entries restored from a cached blob (Marshal-loaded
101
+ # `RBS::Environment` loses real file-path attribution; every
102
+ # buffer reports `"<cached>"`). When every entry carries
103
+ # this sentinel the partition_classes routine returns
104
+ # `[0, total]` AND `attribution_available: false`, which
105
+ # the format routine consumes to suppress the misleading
106
+ # breakdown row.
107
+ CACHED_SENTINEL = "<cached>"
108
+
109
+ # Computes `(project_sig, bundled)` counts from a frozen
110
+ # `Hash<class_name => source_path>` snapshot and the
111
+ # configured `signature_paths`. `project_sig` is the count
112
+ # of classes whose source path begins with any of the
113
+ # signature path prefixes (after expansion to absolute
114
+ # paths); `bundled` is the remainder.
115
+ def self.partition_classes(class_decl_paths:, signature_paths:)
116
+ prefixes = Array(signature_paths).map { |p| File.expand_path(p.to_s) }
117
+ return [0, class_decl_paths.size] if prefixes.empty?
118
+
119
+ project = 0
120
+ class_decl_paths.each_value do |path|
121
+ expanded = File.expand_path(path)
122
+ project += 1 if prefixes.any? { |prefix| expanded.start_with?("#{prefix}/") || expanded == prefix }
123
+ end
124
+ [project, class_decl_paths.size - project]
125
+ end
126
+
127
+ # True when at least one entry in `class_decl_paths` carries
128
+ # a real source file path (i.e. not the cached-sentinel
129
+ # marker). Used by callers to decide whether the
130
+ # `project_sig` / `bundled` split is meaningful.
131
+ def self.attribution_available?(class_decl_paths:)
132
+ return false if class_decl_paths.empty?
133
+
134
+ class_decl_paths.each_value.any? { |path| path != CACHED_SENTINEL }
135
+ end
136
+
137
+ # Writes a human-facing rendering of the stats to `out`
138
+ # (typically `$stderr` from the CLI). Format is intentionally
139
+ # plain text — JSON consumers should parse the structured
140
+ # output of `rigor check --format=json` and consult `stats`
141
+ # there.
142
+ def format(out, prefix: "")
143
+ out.puts("#{prefix}Check targets")
144
+ out.puts("#{prefix} Ruby source files: #{@target_files}")
145
+ out.puts("#{prefix}Type universe (symbol discovery; not analyzed for diagnostics)")
146
+ out.puts("#{prefix} RBS classes available: #{@rbs_classes_total}")
147
+ if @rbs_attribution_available
148
+ out.puts("#{prefix} project sig/: #{@rbs_classes_project_sig}")
149
+ out.puts("#{prefix} bundled (core+stdlib+gems): #{@rbs_classes_bundled}")
150
+ elsif @rbs_classes_total.positive?
151
+ out.puts("#{prefix} (source attribution unavailable on cache-hit runs; --no-cache surfaces it)")
152
+ end
153
+ if @gem_walk_gems.positive?
154
+ out.puts("#{prefix} Gem source-walk classes: #{@gem_walk_classes} " \
155
+ "(across #{@gem_walk_gems} #{@gem_walk_gems == 1 ? 'gem' : 'gems'} " \
156
+ "via dependencies.source_inference)")
157
+ end
158
+ out.puts("#{prefix}Process")
159
+ out.puts("#{prefix} Wall time: #{Kernel.format('%.2fs', @wall_seconds)}")
160
+ out.puts("#{prefix} Memory peak: #{format_bytes(@peak_rss_bytes)}")
161
+ end
162
+
163
+ def to_h
164
+ {
165
+ target_files: @target_files,
166
+ rbs_classes_total: @rbs_classes_total,
167
+ rbs_classes_project_sig: @rbs_classes_project_sig,
168
+ rbs_classes_bundled: @rbs_classes_bundled,
169
+ rbs_attribution_available: @rbs_attribution_available,
170
+ gem_walk_classes: @gem_walk_classes,
171
+ gem_walk_gems: @gem_walk_gems,
172
+ wall_seconds: @wall_seconds,
173
+ peak_rss_bytes: @peak_rss_bytes
174
+ }
175
+ end
176
+
177
+ private
178
+
179
+ def format_bytes(bytes)
180
+ return "unavailable" if bytes.nil?
181
+
182
+ units = %w[B KB MB GB TB]
183
+ size = bytes.to_f
184
+ index = 0
185
+ while size >= 1024 && index < units.size - 1
186
+ size /= 1024
187
+ index += 1
188
+ end
189
+ Kernel.format("%<size>.1f %<unit>s", size: size, unit: units[index])
190
+ end
191
+ end
192
+ end
193
+ end