rigortype 0.4.2 → 0.4.4
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 +1 -1
- data/data/core_overlay/ostruct.rbs +40 -0
- data/docs/handbook/11-sig-gen.md +28 -5
- data/docs/manual/02-cli-reference.md +23 -4
- data/docs/manual/03-configuration.md +21 -8
- data/docs/manual/04-diagnostics.md +44 -2
- data/docs/manual/05-inspecting-types.md +50 -0
- data/docs/manual/07-plugins.md +1 -1
- data/docs/manual/10-mcp-server.md +32 -3
- data/docs/manual/12-caching.md +18 -1
- data/docs/manual/14-rails-quickstart.md +2 -2
- data/docs/manual/18-removing-dead-code.md +26 -9
- data/docs/manual/plugins/rigor-rails-routes.md +2 -2
- data/docs/manual/plugins/rigor-shoulda-matchers.md +4 -1
- data/exe/rigor +12 -17
- data/lib/rigor/analysis/check_rules/rule_ids.rb +64 -0
- data/lib/rigor/analysis/check_rules.rb +68 -57
- data/lib/rigor/analysis/effects_cache_probe.rb +1 -1
- data/lib/rigor/analysis/incremental.rb +12 -0
- data/lib/rigor/analysis/incremental_run_slot.rb +3 -3
- data/lib/rigor/analysis/incremental_session.rb +207 -40
- data/lib/rigor/analysis/path_expansion.rb +62 -6
- data/lib/rigor/analysis/plugin_fact_fingerprint.rb +124 -37
- data/lib/rigor/analysis/reachability/graph.rb +139 -15
- data/lib/rigor/analysis/reachability/scan.rb +374 -173
- data/lib/rigor/analysis/reachability/scan_cache.rb +4 -2
- data/lib/rigor/analysis/rule_catalog.rb +6 -1
- data/lib/rigor/analysis/run_cache_key.rb +31 -7
- data/lib/rigor/analysis/run_cache_probe.rb +6 -4
- data/lib/rigor/analysis/run_stats.rb +28 -6
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +74 -13
- data/lib/rigor/analysis/runner/pool_coordinator.rb +133 -19
- data/lib/rigor/analysis/runner/project_pre_passes.rb +46 -1
- data/lib/rigor/analysis/runner.rb +62 -18
- data/lib/rigor/analysis/severity_stamp.rb +34 -1
- data/lib/rigor/analysis/worker_session.rb +58 -2
- data/lib/rigor/cache/descriptor.rb +40 -6
- data/lib/rigor/cache/engine_source.rb +21 -7
- data/lib/rigor/cache/incremental_snapshot.rb +56 -12
- data/lib/rigor/cli/check_command.rb +138 -39
- data/lib/rigor/cli/docs_command.rb +5 -4
- data/lib/rigor/cli/lens_command.rb +144 -0
- data/lib/rigor/cli/lsp_command.rb +5 -2
- data/lib/rigor/cli/mcp_command.rb +1 -1
- data/lib/rigor/cli/skill_command.rb +3 -3
- data/lib/rigor/cli/skill_describe.rb +1 -1
- data/lib/rigor/cli/type_of_command.rb +2 -1
- data/lib/rigor/cli/unused_command.rb +157 -24
- data/lib/rigor/cli.rb +30 -4
- data/lib/rigor/config_audit.rb +111 -15
- data/lib/rigor/configuration/dependencies.rb +19 -11
- data/lib/rigor/configuration.rb +15 -0
- data/lib/rigor/environment/bundle_sig_discovery.rb +11 -7
- data/lib/rigor/environment/installed_gem_set.rb +38 -11
- data/lib/rigor/environment/lockfile_resolver.rb +37 -6
- data/lib/rigor/environment/missing_gem_constant_index.rb +22 -7
- data/lib/rigor/environment/rbs_loader.rb +79 -10
- data/lib/rigor/environment/required_features.rb +4 -2
- data/lib/rigor/environment.rb +12 -5
- data/lib/rigor/inference/block_call_timing.rb +5 -0
- data/lib/rigor/inference/builtins/cruby_definers.rb +22 -5
- data/lib/rigor/inference/captured_locals.rb +69 -6
- data/lib/rigor/inference/definer_resolution.rb +16 -7
- data/lib/rigor/inference/expression_typer.rb +117 -29
- data/lib/rigor/inference/guard_rebinding.rb +7 -2
- data/lib/rigor/inference/in_effect_refinements/scope_reads.rb +21 -17
- data/lib/rigor/inference/last_line/self_evidence.rb +1 -1
- data/lib/rigor/inference/match_rebinding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/argument_nodes.rb +25 -0
- data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +3 -2
- data/lib/rigor/inference/method_dispatcher/match_data_folding.rb +2 -2
- data/lib/rigor/inference/method_dispatcher/member_shape_projection.rb +3 -3
- data/lib/rigor/inference/method_dispatcher/optional_count.rb +63 -0
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +4 -16
- data/lib/rigor/inference/method_dispatcher/struct_folding.rb +4 -7
- data/lib/rigor/inference/method_dispatcher/struct_materialization.rb +8 -26
- data/lib/rigor/inference/method_dispatcher/unit_denominator_rational.rb +67 -0
- data/lib/rigor/inference/method_dispatcher.rb +52 -3
- data/lib/rigor/inference/module_function_state.rb +80 -13
- data/lib/rigor/inference/narrowing.rb +184 -68
- data/lib/rigor/inference/project_patched_definedness_guard.rb +86 -0
- data/lib/rigor/inference/project_patched_methods.rb +2 -2
- data/lib/rigor/inference/project_patched_scanner.rb +38 -24
- data/lib/rigor/inference/refined_dispatch/class_object.rb +350 -0
- data/lib/rigor/inference/refined_dispatch/own_method.rb +112 -40
- data/lib/rigor/inference/refined_dispatch.rb +62 -14
- data/lib/rigor/inference/reflective_ivar_write.rb +116 -0
- data/lib/rigor/inference/repeated_or_writes.rb +416 -1
- data/lib/rigor/inference/scope_indexer.rb +565 -61
- data/lib/rigor/inference/self_call_ivar_writes.rb +341 -0
- data/lib/rigor/inference/statement_evaluator.rb +307 -60
- data/lib/rigor/inference/untyped_rejoin.rb +75 -0
- data/lib/rigor/language_server/project_context.rb +56 -5
- data/lib/rigor/lens/anchor.rb +115 -0
- data/lib/rigor/lens/contract_reader.rb +133 -0
- data/lib/rigor/lens/document.rb +113 -0
- data/lib/rigor/lens/file_reader.rb +802 -0
- data/lib/rigor/lens/members.rb +203 -0
- data/lib/rigor/lens/parameters.rb +91 -0
- data/lib/rigor/lens/plugins.rb +40 -0
- data/lib/rigor/lens/run.rb +322 -0
- data/lib/rigor/lens/slot.rb +79 -0
- data/lib/rigor/lens.rb +41 -0
- data/lib/rigor/mcp/server.rb +61 -20
- data/lib/rigor/plugin/box_probe.rb +154 -7
- data/lib/rigor/project_environment.rb +23 -1
- data/lib/rigor/protection/mutation_cache.rb +2 -1
- data/lib/rigor/scope.rb +33 -17
- data/lib/rigor/sig_gen/generator.rb +45 -5
- data/lib/rigor/sig_gen/layout_index.rb +36 -0
- data/lib/rigor/sig_gen/method_candidate.rb +8 -0
- data/lib/rigor/sig_gen/path_mapper.rb +18 -2
- data/lib/rigor/sig_gen/writer.rb +47 -34
- data/lib/rigor/version.rb +1 -1
- data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers/serializer_discoverer.rb +29 -3
- data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers.rb +5 -1
- data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema.rb +5 -1
- data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +5 -1
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/discoverer.rb +28 -6
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +1 -1
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +5 -1
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +155 -12
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +10 -8
- data/sig/rigor/scope.rbs +3 -0
- data/sig/rigor.rbs +6 -3
- data/skills/rigor-project-init/references/02-configure.md +2 -2
- metadata +21 -1
|
@@ -18,7 +18,8 @@ module Rigor
|
|
|
18
18
|
# Nominal[T] uniformly, so the distinction with `be_a` is observed at the carrier level but not the
|
|
19
19
|
# runtime).
|
|
20
20
|
# - `be_nil` — narrows `x` to `Constant<nil>`.
|
|
21
|
-
# - `eq(LITERAL)` for a literal-value argument —
|
|
21
|
+
# - `eq(LITERAL)` / `eql(LITERAL)` for a literal-value argument — meets `x` with `Constant<literal>`
|
|
22
|
+
# (see "Equality is the local's own method" below).
|
|
22
23
|
#
|
|
23
24
|
# The matchers below this floor (`be_truthy` / `be_falsey` / `be_within` / `be > / < / >=` /
|
|
24
25
|
# `include` / `start_with` / `match(regex)` / `raise_error` / receive-style mocks) require either
|
|
@@ -35,16 +36,47 @@ module Rigor
|
|
|
35
36
|
# fall through.
|
|
36
37
|
# 3. The matcher is one of the recognised forms above (`be_a` / `be_kind_of` / `be_instance_of` /
|
|
37
38
|
# `be_an_instance_of` / `be_nil` / `eq(literal)` / `eql(literal)` / `match(/regex/)`).
|
|
39
|
+
#
|
|
40
|
+
# ## Equality is the local's own method
|
|
41
|
+
#
|
|
42
|
+
# `expect(x).to eq(lit)` passes when `x == lit` and `eql(lit)` when `x.eql?(lit)`: the local is the
|
|
43
|
+
# receiver, so its own `==` / `eql?` decides, and a class with a custom `==` can equal even a `nil`,
|
|
44
|
+
# boolean or Symbol literal. A positive equality fact therefore never replaces the local's type (issue
|
|
45
|
+
# #1807). It is a meet with the local's current type, member by member:
|
|
46
|
+
#
|
|
47
|
+
# - a member of the literal's own core class (`Integer`, `Float`, `String`, `Symbol`, `NilClass`,
|
|
48
|
+
# `TrueClass`, `FalseClass`) narrows to the literal, a refinement of that class (`non-empty-string`,
|
|
49
|
+
# `decimal-int-string`, `non-zero-int`) included; a `Constant` member is kept when it equals the
|
|
50
|
+
# literal and dropped otherwise; a `Float` member meeting `0.0` (which `-0.0` also equals) is kept;
|
|
51
|
+
# - a core numeric member of another class (`Integer`, `Float`, `Rational`, `Complex`) is kept under
|
|
52
|
+
# `eq` (`1.0 == 1`) and dropped under `eql` (`1.0.eql?(1)` is false);
|
|
53
|
+
# - any other core member (`nil` against `9`, a `String` against `:a`) is dropped: built-in equality
|
|
54
|
+
# across those classes is false;
|
|
55
|
+
# - a member of a core class is kept unnarrowed when the project's source defines the operator on that
|
|
56
|
+
# class or on a core ancestor up to the one that owns the operator at runtime (`class Symbol; def
|
|
57
|
+
# ==`, `class Numeric; def eql?` for an `Integer`, `class Object; def ==` for `nil`): its own method
|
|
58
|
+
# decides.
|
|
59
|
+
#
|
|
60
|
+
# When any member is `Dynamic` or of any other class (a user class, `Object`, `Numeric`, `BigDecimal`),
|
|
61
|
+
# the analyzer emits no fact and the local keeps its type. It also emits none when the project's source
|
|
62
|
+
# defines the operator on the literal's class or those ancestors of it — a core `==` calls back into the
|
|
63
|
+
# literal's (`Integer#==` runs `other == self` for a non-numeric argument, `String#==` for one that
|
|
64
|
+
# responds to `to_str`), so a custom one there can make any member equal — and for an unbound local,
|
|
65
|
+
# when no member survives, and without a scope. A reopening that defines other methods only
|
|
66
|
+
# (`class String; def shout`) does not count.
|
|
67
|
+
#
|
|
68
|
+
# The negative forms (`not_to` / `to_not`) still emit a negative `Constant` fact, which the engine's fact
|
|
69
|
+
# narrowing does not act on, so the local keeps its type there too.
|
|
38
70
|
module MatcherAnalyzer
|
|
39
71
|
module_function
|
|
40
72
|
|
|
41
73
|
# @param call_node — the call whose contribution we're computing. Returns nil when
|
|
42
74
|
# the call shape does not match `expect(local).to matcher`.
|
|
43
|
-
# @param
|
|
44
|
-
#
|
|
45
|
-
#
|
|
46
|
-
#
|
|
47
|
-
def contribution_for(call_node,
|
|
75
|
+
# @param scope — the scope at the call, or nil. Its environment resolves a matcher's class-name
|
|
76
|
+
# argument to a `Type::Nominal` (without one, a bare `Nominal[<name>]` carrier — sound, the receiver
|
|
77
|
+
# constant may be a user class not in RBS); the local's current type in it is what a positive
|
|
78
|
+
# `eq` / `eql` fact meets (without one, no such fact).
|
|
79
|
+
def contribution_for(call_node, scope:)
|
|
48
80
|
verb = assertion_verb(call_node)
|
|
49
81
|
return nil if verb.nil?
|
|
50
82
|
return nil unless call_node.receiver.is_a?(Prism::CallNode)
|
|
@@ -58,14 +90,15 @@ module Rigor
|
|
|
58
90
|
matcher = matcher_call(call_node)
|
|
59
91
|
return nil if matcher.nil?
|
|
60
92
|
|
|
61
|
-
|
|
93
|
+
negative = verb == :negative
|
|
94
|
+
narrowed_type = narrowed_type_for(matcher, target_local, negative: negative, scope: scope)
|
|
62
95
|
return nil if narrowed_type.nil?
|
|
63
96
|
|
|
64
97
|
fact = Rigor::FlowContribution::Fact.new(
|
|
65
98
|
target_kind: :local,
|
|
66
99
|
target_name: target_local,
|
|
67
100
|
type: narrowed_type,
|
|
68
|
-
negative:
|
|
101
|
+
negative: negative
|
|
69
102
|
)
|
|
70
103
|
Rigor::FlowContribution.new(post_return_facts: [fact])
|
|
71
104
|
end
|
|
@@ -105,7 +138,8 @@ module Rigor
|
|
|
105
138
|
# narrowed type. Returns nil when the matcher is
|
|
106
139
|
# unrecognised or its argument shape does not match the
|
|
107
140
|
# supported envelope.
|
|
108
|
-
def narrowed_type_for(matcher,
|
|
141
|
+
def narrowed_type_for(matcher, target_local, negative:, scope:)
|
|
142
|
+
environment = scope&.environment
|
|
109
143
|
return nil unless matcher.is_a?(Prism::CallNode) && matcher.receiver.nil?
|
|
110
144
|
|
|
111
145
|
case matcher.name
|
|
@@ -116,7 +150,7 @@ module Rigor
|
|
|
116
150
|
|
|
117
151
|
Rigor::Type::Combinator.constant_of(nil)
|
|
118
152
|
when :eq, :eql
|
|
119
|
-
|
|
153
|
+
equality_type_for_literal_arg(matcher, target_local, negative: negative, scope: scope)
|
|
120
154
|
when :match
|
|
121
155
|
# `match(/regex/)` narrows x to String. `match("...")` or `match(arbitrary_object)` falls
|
|
122
156
|
# through — the broader matcher dispatch needs the receiver to be a String, but we can only
|
|
@@ -140,14 +174,123 @@ module Rigor
|
|
|
140
174
|
end
|
|
141
175
|
end
|
|
142
176
|
|
|
143
|
-
def
|
|
177
|
+
def equality_type_for_literal_arg(matcher, target_local, negative:, scope:)
|
|
144
178
|
args = matcher.arguments&.arguments || []
|
|
145
179
|
return nil unless args.size == 1
|
|
146
180
|
|
|
147
181
|
literal_value = literal_value_for(args.first)
|
|
148
182
|
return nil if literal_value.equal?(NO_LITERAL)
|
|
183
|
+
return Rigor::Type::Combinator.constant_of(literal_value) if negative
|
|
184
|
+
return nil if scope.nil?
|
|
185
|
+
|
|
186
|
+
operator = matcher.name == :eql ? :eql? : :==
|
|
187
|
+
equality_meet(scope.local(target_local), literal_value, operator, scope)
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
# --- equality meet (`eq` / `eql`; see "Equality is the local's own method") ---
|
|
191
|
+
|
|
192
|
+
# Core classes whose `==` / `eql?` Rigor can evaluate against a literal: across any two of them the
|
|
193
|
+
# answer is built in, unless the project defines the operator on one of them.
|
|
194
|
+
EQUALITY_CORE_CLASSES = [Integer, Float, Rational, Complex, String, Symbol, NilClass, TrueClass,
|
|
195
|
+
FalseClass].freeze
|
|
196
|
+
Ractor.make_shareable(EQUALITY_CORE_CLASSES)
|
|
197
|
+
private_constant :EQUALITY_CORE_CLASSES
|
|
198
|
+
|
|
199
|
+
NUMERIC_CLASS_NAMES = %w[Integer Float Rational Complex].freeze
|
|
200
|
+
Ractor.make_shareable(NUMERIC_CLASS_NAMES)
|
|
201
|
+
private_constant :NUMERIC_CLASS_NAMES
|
|
202
|
+
|
|
203
|
+
# For each core class and operator, the class and its ancestors up to and including the one that owns
|
|
204
|
+
# the operator at runtime (`Integer#eql?` is `Numeric`'s, `nil == x` is `BasicObject`'s). A project
|
|
205
|
+
# definition on any of them is the method the member runs; one above the owner is shadowed by it.
|
|
206
|
+
OPERATOR_OWNER_CHAINS = EQUALITY_CORE_CLASSES.each_with_object({}) do |klass, chains|
|
|
207
|
+
%i[== eql?].each do |operator|
|
|
208
|
+
ancestors = klass.ancestors
|
|
209
|
+
owner_index = ancestors.index(klass.instance_method(operator).owner)
|
|
210
|
+
chains[[klass.name, operator]] = ancestors[0..owner_index].map(&:name).freeze
|
|
211
|
+
end
|
|
212
|
+
end.freeze
|
|
213
|
+
Ractor.make_shareable(OPERATOR_OWNER_CHAINS)
|
|
214
|
+
private_constant :OPERATOR_OWNER_CHAINS
|
|
215
|
+
|
|
216
|
+
# Marks a member whose equality with the literal cannot be decided, so the whole meet gives up.
|
|
217
|
+
UNDECIDED = Object.new.freeze
|
|
218
|
+
private_constant :UNDECIDED
|
|
219
|
+
|
|
220
|
+
# The members of `current` that `x <operator> literal` admits, narrowed where the member's class is
|
|
221
|
+
# the literal's, or nil when any member is undecided or none survives. An unbound local (nil) is one
|
|
222
|
+
# undecided member.
|
|
223
|
+
def equality_meet(current, literal, operator, scope)
|
|
224
|
+
return nil if project_defines?(literal.class.name, operator, scope)
|
|
225
|
+
|
|
226
|
+
members = current.is_a?(Rigor::Type::Union) ? current.members : [current]
|
|
227
|
+
kept = []
|
|
228
|
+
members.each do |member|
|
|
229
|
+
met = meet_member(member, literal, operator, scope)
|
|
230
|
+
return nil if met.equal?(UNDECIDED)
|
|
149
231
|
|
|
150
|
-
|
|
232
|
+
kept << met unless met.nil?
|
|
233
|
+
end
|
|
234
|
+
return nil if kept.empty?
|
|
235
|
+
|
|
236
|
+
Rigor::Type::Combinator.union(*kept)
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
# One member's part of the meet: the member itself or its narrowing, nil when no value of it equals
|
|
240
|
+
# the literal, or UNDECIDED.
|
|
241
|
+
def meet_member(member, literal, operator, scope)
|
|
242
|
+
class_name = member_class_name(member)
|
|
243
|
+
return UNDECIDED if class_name.nil?
|
|
244
|
+
return member if project_defines?(class_name, operator, scope)
|
|
245
|
+
|
|
246
|
+
case member
|
|
247
|
+
when Rigor::Type::Constant then member.value.public_send(operator, literal) ? member : nil
|
|
248
|
+
when Rigor::Type::IntegerRange
|
|
249
|
+
literal.is_a?(Integer) && !member.covers?(literal) ? nil : meet_class(member, class_name, literal, operator)
|
|
250
|
+
else meet_class(member, class_name, literal, operator)
|
|
251
|
+
end
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
# The core class a member's values belong to, or nil for a member that is not wholly of one. A
|
|
255
|
+
# refinement (`Difference`, `Refined`) belongs to its base's class, an `Intersection` to the class all
|
|
256
|
+
# its members share.
|
|
257
|
+
def member_class_name(member)
|
|
258
|
+
case member
|
|
259
|
+
when Rigor::Type::Difference, Rigor::Type::Refined then member_class_name(member.base)
|
|
260
|
+
when Rigor::Type::Intersection
|
|
261
|
+
names = member.members.map { |part| member_class_name(part) }.uniq
|
|
262
|
+
names.size == 1 ? names.first : nil
|
|
263
|
+
when Rigor::Type::FloatRange then "Float"
|
|
264
|
+
when Rigor::Type::Constant
|
|
265
|
+
klass = EQUALITY_CORE_CLASSES.find { |core| member.value.instance_of?(core) }
|
|
266
|
+
klass&.name
|
|
267
|
+
when Rigor::Type::IntegerRange then "Integer"
|
|
268
|
+
when Rigor::Type::Nominal
|
|
269
|
+
name = member.class_name
|
|
270
|
+
member.type_args.empty? && EQUALITY_CORE_CLASSES.any? { |core| core.name == name } ? name : nil
|
|
271
|
+
end
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
def meet_class(member, class_name, literal, operator)
|
|
275
|
+
literal_class = literal.class.name
|
|
276
|
+
if class_name == literal_class
|
|
277
|
+
literal.is_a?(Float) && literal.zero? ? member : Rigor::Type::Combinator.constant_of(literal)
|
|
278
|
+
elsif NUMERIC_CLASS_NAMES.include?(class_name) && NUMERIC_CLASS_NAMES.include?(literal_class)
|
|
279
|
+
operator == :== ? member : nil
|
|
280
|
+
end
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
# Whether the project's source defines `operator` on the core class `class_name` or on a core ancestor
|
|
284
|
+
# up to the operator's runtime owner (OPERATOR_OWNER_CHAINS): a reopening's `def`, an included or
|
|
285
|
+
# prepended project module, a `define_method`. The def-node walk sees other files' `def`s and records
|
|
286
|
+
# the dependency on them; the discovery table adds `define_method`. An RBS declaration is not
|
|
287
|
+
# consulted: once the project reopens `String` for anything, `String#==`'s declaring owner is a
|
|
288
|
+
# project class, which says nothing about who wrote `==`.
|
|
289
|
+
def project_defines?(class_name, operator, scope)
|
|
290
|
+
OPERATOR_OWNER_CHAINS.fetch([class_name, operator]).any? do |name|
|
|
291
|
+
!scope.user_def_through_ancestors(name, operator).first.nil? ||
|
|
292
|
+
scope.discovered_method_through_ancestors?(name, operator, :instance)
|
|
293
|
+
end
|
|
151
294
|
end
|
|
152
295
|
|
|
153
296
|
def string_type_for_regex_arg(matcher)
|
|
@@ -86,7 +86,7 @@ module Rigor
|
|
|
86
86
|
# ADR-37 slice 2 — matcher narrowing (`expect(x).to be_a(T)` → `post_return_facts` on `x`),
|
|
87
87
|
# method-gated by the engine on the expectation verbs.
|
|
88
88
|
narrowing_facts methods: %i[to not_to to_not] do |call_node, scope|
|
|
89
|
-
MatcherAnalyzer.contribution_for(call_node,
|
|
89
|
+
MatcherAnalyzer.contribution_for(call_node, scope: scope)&.post_return_facts
|
|
90
90
|
end
|
|
91
91
|
|
|
92
92
|
# ADR-52 slice 5a — binds local reads in `it` / spec bodies to their `let(:name) { ... }` block's
|
|
@@ -98,13 +98,15 @@ module Rigor
|
|
|
98
98
|
let_binding_return_type(call_node, scope)
|
|
99
99
|
end
|
|
100
100
|
|
|
101
|
-
# ADR-88 WD1 — both contributions are per-file: the matcher narrowing facts come from the call's own AST
|
|
102
|
-
# and
|
|
103
|
-
#
|
|
104
|
-
#
|
|
105
|
-
#
|
|
106
|
-
#
|
|
107
|
-
#
|
|
101
|
+
# ADR-88 WD1 — both contributions are per-file: the matcher narrowing facts come from the call's own AST
|
|
102
|
+
# and, for `eq` / `eql`, from the local's current type and the scope's project discovery (whether the
|
|
103
|
+
# project defines `==` / `eql?` on a core class); the `let`-binding return types come from each spec
|
|
104
|
+
# file's OWN `describe`/`let` structure (`let` is file-scoped). A spec file's edit changes its own
|
|
105
|
+
# content (re-analysed already). The one cross-file input — the optional factorybot `:factory_index`
|
|
106
|
+
# fact this plugin consumes — is itself an ADR-9 publication captured in the incremental fact-surface
|
|
107
|
+
# digest via factorybot's own channel, so it need not appear here. A stable sentinel declares "no OWN
|
|
108
|
+
# cross-file fact surface", keeping the plugin incremental-capable; `--verify-incremental` backstops
|
|
109
|
+
# the claim.
|
|
108
110
|
def incremental_state_fingerprint
|
|
109
111
|
"per-file-lets"
|
|
110
112
|
end
|
data/sig/rigor/scope.rbs
CHANGED
|
@@ -247,6 +247,7 @@ module Rigor
|
|
|
247
247
|
def optimistic_local_miss: (String | Symbol name) -> Object?
|
|
248
248
|
def optimistic_ivar_miss: (String | Symbol name) -> Object?
|
|
249
249
|
def with_local: (String | Symbol name, Type::t type) -> Scope
|
|
250
|
+
def with_rejoined_locals: (untyped) -> Rigor::Scope
|
|
250
251
|
def with_ivar: (String | Symbol name, Type::t type) -> Scope
|
|
251
252
|
def with_local_origin: (String | Symbol name, Symbol? cause) -> Scope
|
|
252
253
|
def with_optimistic_local: (String | Symbol name, Symbol? cause, ?miss: Object?) -> Scope
|
|
@@ -299,6 +300,8 @@ module Rigor
|
|
|
299
300
|
def inferred_param?: (String | Symbol name) -> bool
|
|
300
301
|
def forget_match_globals: () -> Scope
|
|
301
302
|
def untyped_match_globals: () -> Scope
|
|
303
|
+
# sig-gen gap: #1154 — the reader over the `@globals` ivar `initialize` assigns from a parameter reaches
|
|
304
|
+
# `untyped`, so the `bool` is hand-written.
|
|
302
305
|
def match_globals_bound?: () -> bool
|
|
303
306
|
def with_match_frame: (untyped body, ?untyped parameters) -> Scope
|
|
304
307
|
# sig-gen gap: #1154 — the frame reader over an ivar assigned in `initialize` from a parameter reaches
|
data/sig/rigor.rbs
CHANGED
|
@@ -83,7 +83,6 @@ module Rigor
|
|
|
83
83
|
|
|
84
84
|
class RunStats
|
|
85
85
|
attr_reader wall_seconds: Float
|
|
86
|
-
attr_reader peak_rss_bytes: Integer?
|
|
87
86
|
attr_reader target_files: Integer
|
|
88
87
|
attr_reader rbs_classes_total: Integer
|
|
89
88
|
attr_reader rbs_classes_project_sig: Integer
|
|
@@ -93,21 +92,25 @@ module Rigor
|
|
|
93
92
|
attr_reader rbs_attribution_available: bool
|
|
94
93
|
|
|
95
94
|
CACHED_SENTINEL: String
|
|
95
|
+
CURRENT_PROCESS_PEAK_RSS: ^() -> Integer?
|
|
96
96
|
|
|
97
|
+
# authored: #1529 memoised lazy read; its type enters only through the deliberate `peak_rss_probe:` initialize parameter.
|
|
98
|
+
def peak_rss_bytes: () -> Integer?
|
|
97
99
|
def self.peak_rss_bytes: () -> Integer?
|
|
98
100
|
def self.partition_classes: (class_decl_paths: Hash[String, String], signature_paths: Array[untyped]) -> [Integer, Integer]
|
|
99
101
|
def self.attribution_available?: (class_decl_paths: Hash[String, String]) -> bool
|
|
100
102
|
|
|
101
103
|
def initialize: (
|
|
102
104
|
wall_seconds: Float,
|
|
103
|
-
peak_rss_bytes: Integer?,
|
|
104
105
|
target_files: Integer,
|
|
105
106
|
rbs_classes_total: Integer,
|
|
106
107
|
rbs_classes_project_sig: Integer,
|
|
107
108
|
rbs_classes_bundled: Integer,
|
|
108
109
|
gem_walk_classes: Integer,
|
|
109
110
|
gem_walk_gems: Integer,
|
|
110
|
-
?rbs_attribution_available: bool
|
|
111
|
+
?rbs_attribution_available: bool,
|
|
112
|
+
?peak_rss_bytes: Integer?,
|
|
113
|
+
?peak_rss_probe: ^() -> Integer?
|
|
111
114
|
) -> void
|
|
112
115
|
def format: (untyped out, ?prefix: String) -> void
|
|
113
116
|
def to_h: () -> Hash[Symbol, untyped]
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rigortype
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.4.
|
|
4
|
+
version: 0.4.4
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Rigor contributors
|
|
@@ -321,6 +321,7 @@ files:
|
|
|
321
321
|
- data/core_overlay/module.rbs
|
|
322
322
|
- data/core_overlay/numeric.rbs
|
|
323
323
|
- data/core_overlay/object_space.rbs
|
|
324
|
+
- data/core_overlay/ostruct.rbs
|
|
324
325
|
- data/core_overlay/pathname.rbs
|
|
325
326
|
- data/core_overlay/proc.rbs
|
|
326
327
|
- data/core_overlay/process.rbs
|
|
@@ -564,6 +565,7 @@ files:
|
|
|
564
565
|
- lib/rigor/cli/explain_command.rb
|
|
565
566
|
- lib/rigor/cli/fused_protection_renderer.rb
|
|
566
567
|
- lib/rigor/cli/fused_protection_report.rb
|
|
568
|
+
- lib/rigor/cli/lens_command.rb
|
|
567
569
|
- lib/rigor/cli/lsp_command.rb
|
|
568
570
|
- lib/rigor/cli/mcp_command.rb
|
|
569
571
|
- lib/rigor/cli/measurement_integrity_warning.rb
|
|
@@ -756,6 +758,7 @@ files:
|
|
|
756
758
|
- lib/rigor/inference/match_rebinding/self_calls.rb
|
|
757
759
|
- lib/rigor/inference/method_dispatcher.rb
|
|
758
760
|
- lib/rigor/inference/method_dispatcher/alias_strict_nominals.rb
|
|
761
|
+
- lib/rigor/inference/method_dispatcher/argument_nodes.rb
|
|
759
762
|
- lib/rigor/inference/method_dispatcher/arithmetic_sequence_elements.rb
|
|
760
763
|
- lib/rigor/inference/method_dispatcher/array_to_h_folding.rb
|
|
761
764
|
- lib/rigor/inference/method_dispatcher/block_folding.rb
|
|
@@ -777,6 +780,7 @@ files:
|
|
|
777
780
|
- lib/rigor/inference/method_dispatcher/math_folding.rb
|
|
778
781
|
- lib/rigor/inference/method_dispatcher/member_shape_projection.rb
|
|
779
782
|
- lib/rigor/inference/method_dispatcher/method_folding.rb
|
|
783
|
+
- lib/rigor/inference/method_dispatcher/optional_count.rb
|
|
780
784
|
- lib/rigor/inference/method_dispatcher/overload_selector.rb
|
|
781
785
|
- lib/rigor/inference/method_dispatcher/process_folding.rb
|
|
782
786
|
- lib/rigor/inference/method_dispatcher/proven_overload.rb
|
|
@@ -795,6 +799,7 @@ files:
|
|
|
795
799
|
- lib/rigor/inference/method_dispatcher/struct_folding.rb
|
|
796
800
|
- lib/rigor/inference/method_dispatcher/struct_materialization.rb
|
|
797
801
|
- lib/rigor/inference/method_dispatcher/time_folding.rb
|
|
802
|
+
- lib/rigor/inference/method_dispatcher/unit_denominator_rational.rb
|
|
798
803
|
- lib/rigor/inference/method_dispatcher/universal_object_dispatch.rb
|
|
799
804
|
- lib/rigor/inference/method_dispatcher/uri_folding.rb
|
|
800
805
|
- lib/rigor/inference/method_parameter_binder.rb
|
|
@@ -812,6 +817,7 @@ files:
|
|
|
812
817
|
- lib/rigor/inference/pre_eval_constants.rb
|
|
813
818
|
- lib/rigor/inference/precision_scanner.rb
|
|
814
819
|
- lib/rigor/inference/project_method_ownership.rb
|
|
820
|
+
- lib/rigor/inference/project_patched_definedness_guard.rb
|
|
815
821
|
- lib/rigor/inference/project_patched_methods.rb
|
|
816
822
|
- lib/rigor/inference/project_patched_scanner.rb
|
|
817
823
|
- lib/rigor/inference/protection_scanner.rb
|
|
@@ -822,8 +828,10 @@ files:
|
|
|
822
828
|
- lib/rigor/inference/refine_census.rb
|
|
823
829
|
- lib/rigor/inference/refine_self.rb
|
|
824
830
|
- lib/rigor/inference/refined_dispatch.rb
|
|
831
|
+
- lib/rigor/inference/refined_dispatch/class_object.rb
|
|
825
832
|
- lib/rigor/inference/refined_dispatch/own_method.rb
|
|
826
833
|
- lib/rigor/inference/refinement_mutation.rb
|
|
834
|
+
- lib/rigor/inference/reflective_ivar_write.rb
|
|
827
835
|
- lib/rigor/inference/repeated_or_writes.rb
|
|
828
836
|
- lib/rigor/inference/return_barrier.rb
|
|
829
837
|
- lib/rigor/inference/rewrite_mutation.rb
|
|
@@ -835,6 +843,7 @@ files:
|
|
|
835
843
|
- lib/rigor/inference/scope_indexer/member_layouts_collector.rb
|
|
836
844
|
- lib/rigor/inference/scope_indexer/mixin_accumulator.rb
|
|
837
845
|
- lib/rigor/inference/scope_indexer/superclasses_collector.rb
|
|
846
|
+
- lib/rigor/inference/self_call_ivar_writes.rb
|
|
838
847
|
- lib/rigor/inference/singleton_hook_decline.rb
|
|
839
848
|
- lib/rigor/inference/singleton_object_constant.rb
|
|
840
849
|
- lib/rigor/inference/statement_evaluator.rb
|
|
@@ -846,6 +855,7 @@ files:
|
|
|
846
855
|
- lib/rigor/inference/synthetic_method_scanner.rb
|
|
847
856
|
- lib/rigor/inference/unknown_store_widening.rb
|
|
848
857
|
- lib/rigor/inference/unthreaded_rebinds.rb
|
|
858
|
+
- lib/rigor/inference/untyped_rejoin.rb
|
|
849
859
|
- lib/rigor/inference/version_guard.rb
|
|
850
860
|
- lib/rigor/inference/void_origin.rb
|
|
851
861
|
- lib/rigor/inference/void_tail_summary.rb
|
|
@@ -868,6 +878,16 @@ files:
|
|
|
868
878
|
- lib/rigor/language_server/signature_help_provider.rb
|
|
869
879
|
- lib/rigor/language_server/synchronized_writer.rb
|
|
870
880
|
- lib/rigor/language_server/uri.rb
|
|
881
|
+
- lib/rigor/lens.rb
|
|
882
|
+
- lib/rigor/lens/anchor.rb
|
|
883
|
+
- lib/rigor/lens/contract_reader.rb
|
|
884
|
+
- lib/rigor/lens/document.rb
|
|
885
|
+
- lib/rigor/lens/file_reader.rb
|
|
886
|
+
- lib/rigor/lens/members.rb
|
|
887
|
+
- lib/rigor/lens/parameters.rb
|
|
888
|
+
- lib/rigor/lens/plugins.rb
|
|
889
|
+
- lib/rigor/lens/run.rb
|
|
890
|
+
- lib/rigor/lens/slot.rb
|
|
871
891
|
- lib/rigor/mcp.rb
|
|
872
892
|
- lib/rigor/mcp/loop.rb
|
|
873
893
|
- lib/rigor/mcp/server.rb
|