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.
- checksums.yaml +4 -4
- data/README.md +69 -56
- data/lib/rigor/analysis/buffer_binding.rb +36 -0
- data/lib/rigor/analysis/check_rules.rb +11 -1
- data/lib/rigor/analysis/dependency_source_inference/index.rb +14 -1
- data/lib/rigor/analysis/dependency_source_inference/return_type_heuristic.rb +105 -0
- data/lib/rigor/analysis/dependency_source_inference/walker.rb +32 -12
- data/lib/rigor/analysis/fact_store.rb +15 -3
- data/lib/rigor/analysis/project_scan.rb +39 -0
- data/lib/rigor/analysis/result.rb +11 -3
- data/lib/rigor/analysis/run_stats.rb +193 -0
- data/lib/rigor/analysis/runner.rb +681 -19
- data/lib/rigor/analysis/worker_session.rb +339 -0
- data/lib/rigor/builtins/hkt_builtins.rb +342 -0
- data/lib/rigor/builtins/imported_refinements.rb +6 -2
- data/lib/rigor/builtins/regex_refinement.rb +17 -12
- data/lib/rigor/builtins/static_return_refinements.rb +120 -0
- data/lib/rigor/cache/rbs_descriptor.rb +3 -1
- data/lib/rigor/cache/store.rb +72 -9
- data/lib/rigor/cli/lsp_command.rb +129 -0
- data/lib/rigor/cli/type_of_command.rb +44 -5
- data/lib/rigor/cli.rb +122 -10
- data/lib/rigor/configuration.rb +168 -7
- data/lib/rigor/environment/bundle_sig_discovery.rb +198 -0
- data/lib/rigor/environment/class_registry.rb +12 -3
- data/lib/rigor/environment/hkt_registry_holder.rb +33 -0
- data/lib/rigor/environment/lockfile_resolver.rb +125 -0
- data/lib/rigor/environment/rbs_collection_discovery.rb +126 -0
- data/lib/rigor/environment/rbs_coverage_report.rb +112 -0
- data/lib/rigor/environment/rbs_loader.rb +238 -7
- data/lib/rigor/environment/reflection.rb +152 -0
- data/lib/rigor/environment/reporters.rb +40 -0
- data/lib/rigor/environment.rb +179 -10
- data/lib/rigor/inference/acceptance.rb +83 -4
- data/lib/rigor/inference/builtins/method_catalog.rb +12 -5
- data/lib/rigor/inference/builtins/numeric_catalog.rb +15 -4
- data/lib/rigor/inference/expression_typer.rb +59 -2
- data/lib/rigor/inference/hkt_body.rb +171 -0
- data/lib/rigor/inference/hkt_body_parser.rb +363 -0
- data/lib/rigor/inference/hkt_reducer.rb +256 -0
- data/lib/rigor/inference/hkt_registry.rb +223 -0
- data/lib/rigor/inference/macro_block_self_type.rb +96 -0
- data/lib/rigor/inference/method_dispatcher/constant_folding.rb +29 -29
- data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +4 -4
- data/lib/rigor/inference/method_dispatcher/method_folding.rb +18 -1
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +126 -31
- data/lib/rigor/inference/method_dispatcher/receiver_affinity.rb +87 -0
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +46 -40
- data/lib/rigor/inference/method_dispatcher.rb +282 -6
- data/lib/rigor/inference/method_parameter_binder.rb +21 -11
- data/lib/rigor/inference/narrowing.rb +127 -8
- data/lib/rigor/inference/project_patched_methods.rb +70 -0
- data/lib/rigor/inference/project_patched_scanner.rb +210 -0
- data/lib/rigor/inference/scope_indexer.rb +156 -12
- data/lib/rigor/inference/statement_evaluator.rb +106 -6
- data/lib/rigor/inference/synthetic_method.rb +86 -0
- data/lib/rigor/inference/synthetic_method_index.rb +82 -0
- data/lib/rigor/inference/synthetic_method_scanner.rb +599 -0
- data/lib/rigor/language_server/buffer_table.rb +63 -0
- data/lib/rigor/language_server/completion_provider.rb +438 -0
- data/lib/rigor/language_server/debouncer.rb +86 -0
- data/lib/rigor/language_server/diagnostic_publisher.rb +167 -0
- data/lib/rigor/language_server/document_symbol_provider.rb +142 -0
- data/lib/rigor/language_server/folding_range_provider.rb +75 -0
- data/lib/rigor/language_server/hover_provider.rb +74 -0
- data/lib/rigor/language_server/hover_renderer.rb +312 -0
- data/lib/rigor/language_server/loop.rb +71 -0
- data/lib/rigor/language_server/project_context.rb +145 -0
- data/lib/rigor/language_server/selection_range_provider.rb +93 -0
- data/lib/rigor/language_server/server.rb +384 -0
- data/lib/rigor/language_server/signature_help_provider.rb +249 -0
- data/lib/rigor/language_server/synchronized_writer.rb +28 -0
- data/lib/rigor/language_server/uri.rb +40 -0
- data/lib/rigor/language_server.rb +29 -0
- data/lib/rigor/plugin/base.rb +63 -0
- data/lib/rigor/plugin/blueprint.rb +60 -0
- data/lib/rigor/plugin/loader.rb +3 -1
- data/lib/rigor/plugin/macro/block_as_method.rb +131 -0
- data/lib/rigor/plugin/macro/external_file.rb +143 -0
- data/lib/rigor/plugin/macro/heredoc_template.rb +315 -0
- data/lib/rigor/plugin/macro/trait_registry.rb +198 -0
- data/lib/rigor/plugin/macro.rb +31 -0
- data/lib/rigor/plugin/manifest.rb +127 -9
- data/lib/rigor/plugin/registry.rb +51 -2
- data/lib/rigor/plugin.rb +1 -0
- data/lib/rigor/rbs_extended/hkt_directives.rb +326 -0
- data/lib/rigor/rbs_extended.rb +82 -2
- data/lib/rigor/sig_gen/generator.rb +12 -3
- data/lib/rigor/trinary.rb +15 -11
- data/lib/rigor/type/app.rb +107 -0
- data/lib/rigor/type/bot.rb +6 -3
- data/lib/rigor/type/combinator.rb +12 -1
- data/lib/rigor/type/integer_range.rb +7 -7
- data/lib/rigor/type/refined.rb +18 -12
- data/lib/rigor/type/top.rb +4 -3
- data/lib/rigor/type.rb +1 -0
- data/lib/rigor/type_node/generic.rb +7 -1
- data/lib/rigor/type_node/identifier.rb +9 -1
- data/lib/rigor/type_node/string_literal.rb +4 -1
- data/lib/rigor/version.rb +1 -1
- data/sig/rigor/environment.rbs +11 -4
- data/sig/rigor/inference.rbs +2 -0
- data/sig/rigor/plugin/blueprint.rbs +7 -0
- data/sig/rigor/plugin/manifest.rbs +1 -1
- data/sig/rigor/plugin/registry.rbs +14 -1
- data/sig/rigor.rbs +37 -2
- 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
|