rigortype 0.1.18 → 0.2.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.
- checksums.yaml +4 -4
- data/README.md +159 -224
- data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +9 -3
- data/lib/rigor/analysis/check_rules/dead_assignment_collector.rb +25 -0
- data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +32 -23
- data/lib/rigor/analysis/check_rules/main_pass_collector.rb +54 -0
- data/lib/rigor/analysis/check_rules/rule_walk.rb +151 -23
- data/lib/rigor/analysis/check_rules/self_closedness_scanner.rb +24 -15
- data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +9 -3
- data/lib/rigor/analysis/check_rules.rb +756 -132
- data/lib/rigor/analysis/dependency_source_inference/index.rb +4 -7
- data/lib/rigor/analysis/dependency_source_inference/walker.rb +2 -18
- data/lib/rigor/analysis/dependency_source_inference.rb +3 -12
- data/lib/rigor/analysis/diagnostic.rb +8 -0
- data/lib/rigor/analysis/fact_store.rb +5 -4
- data/lib/rigor/analysis/rule_catalog.rb +153 -6
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +19 -18
- data/lib/rigor/analysis/runner/project_pre_passes.rb +13 -9
- data/lib/rigor/analysis/runner.rb +75 -27
- data/lib/rigor/analysis/self_call_resolution_recorder.rb +3 -4
- data/lib/rigor/analysis/worker_session.rb +31 -25
- data/lib/rigor/bleeding_edge.rb +123 -0
- data/lib/rigor/builtins/predefined_constant_refinements.rb +151 -0
- data/lib/rigor/cache/descriptor.rb +86 -8
- data/lib/rigor/cache/rbs_descriptor.rb +2 -1
- data/lib/rigor/cache/store.rb +5 -3
- data/lib/rigor/cli/annotate_command.rb +122 -16
- data/lib/rigor/cli/baseline_command.rb +4 -3
- data/lib/rigor/cli/check_command.rb +118 -16
- data/lib/rigor/cli/coverage_command.rb +148 -16
- data/lib/rigor/cli/coverage_scan.rb +57 -0
- data/lib/rigor/cli/explain_command.rb +2 -0
- data/lib/rigor/cli/lsp_command.rb +3 -7
- data/lib/rigor/cli/mutation_protection_renderer.rb +63 -0
- data/lib/rigor/cli/mutation_protection_report.rb +73 -0
- data/lib/rigor/cli/options.rb +9 -0
- data/lib/rigor/cli/plugins_command.rb +4 -5
- data/lib/rigor/cli/plugins_renderer.rb +0 -2
- data/lib/rigor/cli/protection_renderer.rb +63 -0
- data/lib/rigor/cli/protection_report.rb +68 -0
- data/lib/rigor/cli/show_bleedingedge_command.rb +114 -0
- data/lib/rigor/cli/sig_gen_command.rb +2 -1
- data/lib/rigor/cli/trace_command.rb +2 -1
- data/lib/rigor/cli/triage_command.rb +8 -4
- data/lib/rigor/cli/triage_renderer.rb +15 -1
- data/lib/rigor/cli/type_of_command.rb +1 -1
- data/lib/rigor/cli/type_scan_command.rb +2 -1
- data/lib/rigor/cli.rb +12 -3
- data/lib/rigor/configuration/dependencies.rb +2 -4
- data/lib/rigor/configuration/severity_profile.rb +13 -1
- data/lib/rigor/configuration.rb +100 -6
- data/lib/rigor/environment/bundle_sig_discovery.rb +61 -13
- data/lib/rigor/environment/class_registry.rb +4 -3
- data/lib/rigor/environment/constant_type_cache_holder.rb +43 -0
- data/lib/rigor/environment/lockfile_resolver.rb +1 -1
- data/lib/rigor/environment/rbs_collection_discovery.rb +1 -2
- data/lib/rigor/environment/rbs_coverage_report.rb +2 -1
- data/lib/rigor/environment/rbs_loader.rb +74 -5
- data/lib/rigor/environment.rb +17 -7
- data/lib/rigor/flow_contribution/fact.rb +1 -1
- data/lib/rigor/flow_contribution.rb +3 -5
- data/lib/rigor/inference/acceptance.rb +17 -9
- data/lib/rigor/inference/block_parameter_binder.rb +2 -3
- data/lib/rigor/inference/body_fixpoint.rb +89 -0
- data/lib/rigor/inference/budget_trace.rb +29 -2
- data/lib/rigor/inference/builtins/comparable_catalog.rb +2 -2
- data/lib/rigor/inference/builtins/enumerable_catalog.rb +2 -2
- data/lib/rigor/inference/builtins/method_catalog.rb +19 -0
- data/lib/rigor/inference/builtins/string_catalog.rb +9 -1
- data/lib/rigor/inference/expression_typer.rb +1072 -71
- data/lib/rigor/inference/hkt_body.rb +8 -11
- data/lib/rigor/inference/hkt_body_parser.rb +10 -12
- data/lib/rigor/inference/hkt_registry.rb +10 -11
- data/lib/rigor/inference/macro_block_self_type.rb +2 -2
- data/lib/rigor/inference/method_dispatcher/array_to_h_folding.rb +60 -0
- data/lib/rigor/inference/method_dispatcher/call_context.rb +1 -4
- data/lib/rigor/inference/method_dispatcher/constant_folding.rb +210 -35
- data/lib/rigor/inference/method_dispatcher/data_folding.rb +9 -73
- data/lib/rigor/inference/method_dispatcher/file_folding.rb +6 -7
- data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +10 -16
- data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +25 -13
- data/lib/rigor/inference/method_dispatcher/member_shape_projection.rb +93 -0
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +1 -3
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +24 -22
- data/lib/rigor/inference/method_dispatcher/reduce_folding.rb +281 -0
- data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +71 -0
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +237 -24
- data/lib/rigor/inference/method_dispatcher/struct_folding.rb +303 -0
- data/lib/rigor/inference/method_dispatcher.rb +112 -49
- data/lib/rigor/inference/method_parameter_binder.rb +56 -2
- data/lib/rigor/inference/multi_target_binder.rb +46 -3
- data/lib/rigor/inference/mutation_widening.rb +147 -11
- data/lib/rigor/inference/narrowing.rb +284 -53
- data/lib/rigor/inference/parameter_inference_collector.rb +367 -0
- data/lib/rigor/inference/project_patched_methods.rb +4 -7
- data/lib/rigor/inference/project_patched_scanner.rb +2 -13
- data/lib/rigor/inference/protection_scanner.rb +86 -0
- data/lib/rigor/inference/scope_indexer.rb +821 -76
- data/lib/rigor/inference/statement_evaluator.rb +1179 -102
- data/lib/rigor/inference/struct_fold_safety.rb +181 -0
- data/lib/rigor/inference/synthetic_method.rb +7 -7
- data/lib/rigor/inference/synthetic_method_scanner.rb +1 -1
- data/lib/rigor/language_server/completion_provider.rb +6 -12
- data/lib/rigor/language_server/diagnostic_publisher.rb +4 -4
- data/lib/rigor/language_server/document_symbol_provider.rb +3 -3
- data/lib/rigor/language_server/hover_provider.rb +2 -3
- data/lib/rigor/language_server/hover_renderer.rb +2 -11
- data/lib/rigor/language_server/server.rb +9 -17
- data/lib/rigor/language_server.rb +4 -5
- data/lib/rigor/plugin/base.rb +245 -87
- data/lib/rigor/plugin/macro/block_as_method.rb +25 -25
- data/lib/rigor/plugin/macro/heredoc_template.rb +4 -7
- data/lib/rigor/plugin/macro/nested_class_template.rb +9 -7
- data/lib/rigor/plugin/macro/trait_registry.rb +3 -6
- data/lib/rigor/plugin/macro.rb +6 -8
- data/lib/rigor/plugin/manifest.rb +49 -90
- data/lib/rigor/plugin/node_rule_walk.rb +59 -14
- data/lib/rigor/plugin/registry.rb +18 -18
- data/lib/rigor/plugin/type_node_resolver.rb +6 -8
- data/lib/rigor/protection/mutation_scanner.rb +120 -0
- data/lib/rigor/protection/mutator.rb +246 -0
- data/lib/rigor/rbs_extended.rb +24 -36
- data/lib/rigor/reflection.rb +4 -7
- data/lib/rigor/scope/discovery_index.rb +16 -2
- data/lib/rigor/scope.rb +185 -16
- data/lib/rigor/sig_gen/generator.rb +8 -0
- data/lib/rigor/sig_gen/observed_call.rb +3 -3
- data/lib/rigor/sig_gen/writer.rb +40 -2
- data/lib/rigor/source/constant_path.rb +62 -0
- data/lib/rigor/source.rb +1 -0
- data/lib/rigor/triage/catalogue.rb +4 -19
- data/lib/rigor/triage.rb +69 -1
- data/lib/rigor/type/bound_method.rb +2 -11
- data/lib/rigor/type/combinator.rb +45 -3
- data/lib/rigor/type/constant.rb +2 -11
- data/lib/rigor/type/data_class.rb +2 -11
- data/lib/rigor/type/data_instance.rb +2 -11
- data/lib/rigor/type/hash_shape.rb +2 -11
- data/lib/rigor/type/integer_range.rb +2 -11
- data/lib/rigor/type/intersection.rb +2 -11
- data/lib/rigor/type/nominal.rb +2 -11
- data/lib/rigor/type/plain_lattice.rb +37 -0
- data/lib/rigor/type/refined.rb +72 -13
- data/lib/rigor/type/singleton.rb +2 -11
- data/lib/rigor/type/struct_class.rb +75 -0
- data/lib/rigor/type/struct_instance.rb +93 -0
- data/lib/rigor/type/tuple.rb +5 -15
- data/lib/rigor/type.rb +2 -0
- data/lib/rigor/version.rb +1 -1
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +1 -1
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +3 -3
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +16 -32
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +5 -13
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +13 -32
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +11 -17
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +34 -100
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +3 -2
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +13 -30
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +4 -4
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +26 -27
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/analyzer.rb +5 -7
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +9 -8
- data/plugins/rigor-devise/lib/rigor/plugin/devise.rb +9 -11
- data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +8 -9
- data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +13 -12
- data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +3 -4
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/analyzer.rb +8 -8
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +9 -11
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_index.rb +7 -8
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +18 -49
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +12 -13
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +15 -23
- data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +4 -4
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +3 -3
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit.rb +10 -21
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +2 -4
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +27 -11
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +22 -35
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +4 -6
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +12 -18
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +16 -23
- data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +0 -1
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_scope_index.rb +3 -4
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_type_resolver.rb +1 -1
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +1 -1
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +21 -27
- data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +0 -1
- data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers.rb +3 -23
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_index.rb +5 -4
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +8 -21
- data/plugins/rigor-sinatra/lib/rigor/plugin/sinatra.rb +1 -1
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +2 -3
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/method_signature.rb +7 -11
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +4 -5
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sigil_detector.rb +6 -9
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/type_translator.rb +5 -15
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +52 -40
- data/sig/rigor/analysis/fact_store.rbs +3 -0
- data/sig/rigor/inference/builtins/method_catalog.rbs +1 -1
- data/sig/rigor/plugin/base.rbs +5 -2
- data/sig/rigor/plugin/manifest.rbs +1 -2
- data/sig/rigor/scope.rbs +18 -1
- data/sig/rigor/type.rbs +37 -1
- data/sig/rigor.rbs +1 -1
- data/skills/rigor-baseline-reduce/references/01-classify.md +27 -0
- data/skills/rigor-plugin-author/SKILL.md +6 -4
- data/skills/rigor-plugin-author/references/02-walker-and-types.md +22 -17
- data/skills/rigor-project-init/references/03-baseline-and-bugs.md +18 -1
- metadata +25 -2
- data/lib/rigor/plugin/macro/external_file.rb +0 -143
|
@@ -249,6 +249,12 @@ module Rigor
|
|
|
249
249
|
# anonymous local-bound form projects to `Data` itself).
|
|
250
250
|
accepts(self_type, project_data_instance_to_nominal(other_type), mode: mode)
|
|
251
251
|
.with_reason("projected DataInstance to Nominal[#{other_type.class_name || 'Data'}]")
|
|
252
|
+
when Type::StructInstance
|
|
253
|
+
# ADR-48 Struct follow-up: same projection as DataInstance — a
|
|
254
|
+
# class-tagged Struct value is exactly one value of its tagging
|
|
255
|
+
# class (the anonymous form projects to `Struct` itself).
|
|
256
|
+
accepts(self_type, project_struct_instance_to_nominal(other_type), mode: mode)
|
|
257
|
+
.with_reason("projected StructInstance to Nominal[#{other_type.class_name || 'Struct'}]")
|
|
252
258
|
when Type::Difference, Type::Refined
|
|
253
259
|
# A refinement carrier's value set is a subset of its
|
|
254
260
|
# base. So if `self` (Nominal) accepts the base, it
|
|
@@ -386,6 +392,10 @@ module Rigor
|
|
|
386
392
|
Type::Combinator.nominal_of(instance.class_name || "Data")
|
|
387
393
|
end
|
|
388
394
|
|
|
395
|
+
def project_struct_instance_to_nominal(instance)
|
|
396
|
+
Type::Combinator.nominal_of(instance.class_name || "Struct")
|
|
397
|
+
end
|
|
398
|
+
|
|
389
399
|
def project_hash_shape_to_nominal(shape)
|
|
390
400
|
return Type::Combinator.nominal_of(Hash) if shape.pairs.empty?
|
|
391
401
|
|
|
@@ -822,15 +832,13 @@ module Rigor
|
|
|
822
832
|
Type::AcceptsResult.no(mode: mode, reasons: reason)
|
|
823
833
|
end
|
|
824
834
|
|
|
825
|
-
#
|
|
826
|
-
# "is D a subclass of C?"
|
|
827
|
-
#
|
|
828
|
-
#
|
|
829
|
-
#
|
|
830
|
-
#
|
|
831
|
-
#
|
|
832
|
-
# so ahead-of-time type checking no longer relies on Ruby
|
|
833
|
-
# loading the application classes.
|
|
835
|
+
# Uses Ruby's actual class hierarchy via Object.const_get to answer
|
|
836
|
+
# "is D a subclass of C?" for core, stdlib, and application classes.
|
|
837
|
+
# When either name fails to resolve we surface "maybe": the caller
|
|
838
|
+
# (overload selector) treats yes/maybe identically, so the conservative
|
|
839
|
+
# answer keeps overload coverage intact. RbsHierarchy exists but this
|
|
840
|
+
# path does not yet consult it; migration to an RBS-driven lookup
|
|
841
|
+
# is deferred.
|
|
834
842
|
def class_subtype_result(target_name:, actual_name:, mode:, kind:)
|
|
835
843
|
return Type::AcceptsResult.yes(mode: mode, reasons: "exact name match") if target_name == actual_name
|
|
836
844
|
|
|
@@ -183,9 +183,8 @@ module Rigor
|
|
|
183
183
|
# `|*rest|` binds an Array of the leftover positional arguments.
|
|
184
184
|
# The expected-types array is per-position, not per-rest; we
|
|
185
185
|
# cannot reliably pick a single element type for rest, so we
|
|
186
|
-
# default to `Array[Dynamic[Top]]`.
|
|
187
|
-
#
|
|
188
|
-
# available.
|
|
186
|
+
# default to `Array[Dynamic[Top]]`. Element-type precision for
|
|
187
|
+
# rest parameters is deferred (demand-gated).
|
|
189
188
|
def bind_rest(params_node, bindings)
|
|
190
189
|
rest = params_node.rest
|
|
191
190
|
return unless rest.respond_to?(:name) && rest&.name
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../type"
|
|
4
|
+
require_relative "budget_trace"
|
|
5
|
+
|
|
6
|
+
module Rigor
|
|
7
|
+
module Inference
|
|
8
|
+
# ADR-56 WD3 — the single capped-fixpoint mechanism shared by the
|
|
9
|
+
# non-escaping block captured-local write-back (slice A) and the
|
|
10
|
+
# loop-body fixpoint (slice B). Computes the continuation binding of a
|
|
11
|
+
# set of locals that a body (a block body or a loop body) may rebind
|
|
12
|
+
# across an unknown number (0..N) of iterations.
|
|
13
|
+
#
|
|
14
|
+
# The body may run zero times, so the seed (pre-state) binding is kept
|
|
15
|
+
# as a join constituent throughout; it may compound (`a = [a]`), so the
|
|
16
|
+
# join is iterated to a fixed point under a hard cap (ADR-41 WD4). On
|
|
17
|
+
# the final permitted iteration value-pinned constituents are widened to
|
|
18
|
+
# their nominal base to force convergence; a local that still moves is
|
|
19
|
+
# collapsed to `Dynamic[top]` — the established escaping-block floor —
|
|
20
|
+
# and a {BudgetTrace::BLOCK_WRITEBACK_CAP} hit is recorded.
|
|
21
|
+
#
|
|
22
|
+
# The mechanism is parameterized over an `evaluate_body` callable so
|
|
23
|
+
# slice B can reuse it: given the current per-name bindings it returns
|
|
24
|
+
# the per-name exit bindings produced by one body evaluation from those
|
|
25
|
+
# bindings (names the body leaves unwritten in a given pass simply do
|
|
26
|
+
# not appear in the returned hash).
|
|
27
|
+
module BodyFixpoint
|
|
28
|
+
# One body evaluation per iteration; ADR-55's shape (cap 3).
|
|
29
|
+
CAP = 3
|
|
30
|
+
|
|
31
|
+
module_function
|
|
32
|
+
|
|
33
|
+
# @param names [Array<Symbol>] the outer locals the body can rebind.
|
|
34
|
+
# @param seed_bindings [Hash{Symbol=>Type}] pre-state binding per name.
|
|
35
|
+
# @param widen [#call] value-pinned widener (Constant -> Nominal).
|
|
36
|
+
# @param evaluate_body [#call] `bindings -> exit_bindings` — evaluates
|
|
37
|
+
# the body once from `bindings` (the per-name current assumption) and
|
|
38
|
+
# returns the per-name exit binding it produced.
|
|
39
|
+
# @return [Hash{Symbol=>Type}] the continuation binding per name.
|
|
40
|
+
def converge(names:, seed_bindings:, widen:, evaluate_body:)
|
|
41
|
+
return {} if names.empty?
|
|
42
|
+
|
|
43
|
+
# Running assumption per name; seeded with the pre-state binding,
|
|
44
|
+
# which stays a join constituent throughout (0-iteration soundness).
|
|
45
|
+
assumption = seed_bindings.dup
|
|
46
|
+
|
|
47
|
+
(0...CAP).each do |iteration|
|
|
48
|
+
last_iteration = iteration == CAP - 1
|
|
49
|
+
exit_bindings = evaluate_body.call(assumption)
|
|
50
|
+
|
|
51
|
+
stable = true
|
|
52
|
+
names.each do |name|
|
|
53
|
+
exit_type = exit_bindings[name]
|
|
54
|
+
next if exit_type.nil? # body did not write it this pass
|
|
55
|
+
|
|
56
|
+
if last_iteration
|
|
57
|
+
# On the final permitted iteration widen BOTH the running
|
|
58
|
+
# assumption and the fresh exit type to their nominal bases
|
|
59
|
+
# before joining: Rigor's `union` keeps `Constant[1]` and
|
|
60
|
+
# `Nominal[Integer]` as distinct members, so an accumulator
|
|
61
|
+
# (`+=`/`*=`) producing a fresh constant per pass would never
|
|
62
|
+
# converge without collapsing both sides first. If the join
|
|
63
|
+
# is still wider than the widened assumption (structural
|
|
64
|
+
# compounding, `a = [a]`), the local floors to `Dynamic[top]`.
|
|
65
|
+
base = widen.call(assumption[name])
|
|
66
|
+
joined = Type::Combinator.union(base, widen.call(exit_type))
|
|
67
|
+
if joined == base
|
|
68
|
+
assumption[name] = joined
|
|
69
|
+
else
|
|
70
|
+
BudgetTrace.hit(BudgetTrace::BLOCK_WRITEBACK_CAP)
|
|
71
|
+
assumption[name] = Type::Combinator.untyped
|
|
72
|
+
end
|
|
73
|
+
else
|
|
74
|
+
joined = Type::Combinator.union(assumption[name], exit_type)
|
|
75
|
+
next if joined == assumption[name]
|
|
76
|
+
|
|
77
|
+
stable = false
|
|
78
|
+
assumption[name] = joined
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
break if stable
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
assumption
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
@@ -20,6 +20,12 @@ module Rigor
|
|
|
20
20
|
# hit the 100-node BFS cap and gave up resolving the self-call.
|
|
21
21
|
# - {HKT_FUEL_EXHAUSTED} — `HktReducer` ran out of its reduction
|
|
22
22
|
# fuel budget and unwound to `app.bound`.
|
|
23
|
+
# - {RECURSION_UNROLL_FUEL} — the constant-arg recursion unroll
|
|
24
|
+
# (ADR-55 slice 1) exhausted its per-entry fuel and fell back to
|
|
25
|
+
# the plain `(receiver, method)` guard (in-cycle call → `Dynamic[top]`).
|
|
26
|
+
# - {RECURSION_FIXPOINT_CAP} — the fixpoint return-summary iteration
|
|
27
|
+
# (ADR-55 slice 2) hit its 3-evaluation cap without converging and
|
|
28
|
+
# collapsed the summary to `untyped` (today's behaviour).
|
|
23
29
|
#
|
|
24
30
|
# Enabled only when `RIGOR_BUDGET_TRACE` is set (to any non-empty
|
|
25
31
|
# value) in the environment, or via {enable!} in tests. When
|
|
@@ -34,8 +40,29 @@ module Rigor
|
|
|
34
40
|
RECURSION_GUARD = :recursion_guard
|
|
35
41
|
ANCESTOR_WALK_LIMIT = :ancestor_walk_limit
|
|
36
42
|
HKT_FUEL_EXHAUSTED = :hkt_fuel_exhausted
|
|
37
|
-
|
|
38
|
-
|
|
43
|
+
# `ExpressionTyper#infer_user_method_return` exhausted its
|
|
44
|
+
# constant-arg unroll fuel (ADR-55 slice 1) and fell back to the
|
|
45
|
+
# plain `(receiver, method)` recursion guard — i.e. the in-cycle
|
|
46
|
+
# call widened to `Dynamic[top]` exactly as it does without the
|
|
47
|
+
# unroll.
|
|
48
|
+
RECURSION_UNROLL_FUEL = :recursion_unroll_fuel
|
|
49
|
+
# `ExpressionTyper#infer_user_method_return` ran the fixpoint
|
|
50
|
+
# return-summary iteration (ADR-55 slice 2) to its 3-evaluation cap
|
|
51
|
+
# without reaching convergence and collapsed the summary to
|
|
52
|
+
# `untyped` — the in-cycle result widens to `Dynamic[top]` exactly
|
|
53
|
+
# as it does without the fixpoint.
|
|
54
|
+
RECURSION_FIXPOINT_CAP = :recursion_fixpoint_cap
|
|
55
|
+
# `BodyFixpoint#converge` (ADR-56 slice A — non-escaping block
|
|
56
|
+
# captured-local write-back) ran its 3-evaluation cap without the
|
|
57
|
+
# written local's join converging and collapsed that local to
|
|
58
|
+
# `Dynamic[top]` (the escaping-block floor). Shared by slice B's
|
|
59
|
+
# loop-body fixpoint.
|
|
60
|
+
BLOCK_WRITEBACK_CAP = :block_writeback_cap
|
|
61
|
+
|
|
62
|
+
CATEGORIES = [
|
|
63
|
+
RECURSION_GUARD, ANCESTOR_WALK_LIMIT, HKT_FUEL_EXHAUSTED, RECURSION_UNROLL_FUEL,
|
|
64
|
+
RECURSION_FIXPOINT_CAP, BLOCK_WRITEBACK_CAP
|
|
65
|
+
].freeze
|
|
39
66
|
|
|
40
67
|
# Distribution (histogram) categories — read-only observations of
|
|
41
68
|
# a value's size at a site, used to choose budget defaults from an
|
|
@@ -11,8 +11,8 @@ module Rigor
|
|
|
11
11
|
# catalog is NOT routed through
|
|
12
12
|
# `MethodDispatcher::ConstantFolding::CATALOG_BY_CLASS`
|
|
13
13
|
# (which dispatches on the receiver's concrete class).
|
|
14
|
-
# The data is
|
|
15
|
-
#
|
|
14
|
+
# The data is wired into `MODULE_CATALOGS` in
|
|
15
|
+
# `MethodDispatcher::ConstantFolding` (ancestor-chain lookup).
|
|
16
16
|
COMPARABLE_CATALOG = MethodCatalog.for_topic(
|
|
17
17
|
"comparable",
|
|
18
18
|
mutating_selectors: {
|
|
@@ -11,8 +11,8 @@ module Rigor
|
|
|
11
11
|
# catalog is NOT routed through
|
|
12
12
|
# `MethodDispatcher::ConstantFolding::CATALOG_BY_CLASS`
|
|
13
13
|
# (which dispatches on the receiver's concrete class).
|
|
14
|
-
# The data is
|
|
15
|
-
#
|
|
14
|
+
# The data is wired into `MODULE_CATALOGS` in
|
|
15
|
+
# `MethodDispatcher::ConstantFolding` (ancestor-chain lookup).
|
|
16
16
|
ENUMERABLE_CATALOG = MethodCatalog.for_topic(
|
|
17
17
|
"enumerable",
|
|
18
18
|
mutating_selectors: {
|
|
@@ -27,6 +27,24 @@ module Rigor
|
|
|
27
27
|
FOLDABLE_PURITIES = Set["leaf", "trivial", "leaf_when_numeric"].freeze
|
|
28
28
|
EMPTY_CATALOG = { "classes" => {} }.freeze
|
|
29
29
|
|
|
30
|
+
# Selectors that are classified `:leaf` by the C-body analysis
|
|
31
|
+
# (they read no global mutable state in the C sense) but whose
|
|
32
|
+
# result is NOT reproducible across Ruby processes, so they must
|
|
33
|
+
# never be folded into a `Constant`:
|
|
34
|
+
#
|
|
35
|
+
# - `hash` — every core `#hash` (`String`/`Symbol`/`Integer`/
|
|
36
|
+
# `Float`/…) is salted with a per-process SipHash seed, so
|
|
37
|
+
# `"x".hash` differs in every process. Folding bakes one
|
|
38
|
+
# process's value into the type and the on-disk cache.
|
|
39
|
+
# - `object_id` / `__id__` — identity-allocated per process.
|
|
40
|
+
#
|
|
41
|
+
# This is a UNIVERSAL block (across every catalogued class)
|
|
42
|
+
# because `hash` / `object_id` are `Object`-level and present on
|
|
43
|
+
# every receiver; a per-class blocklist would silently miss a
|
|
44
|
+
# class. The deterministic siblings (`inspect`, `to_s`) are
|
|
45
|
+
# unaffected.
|
|
46
|
+
NON_REPRODUCIBLE_SELECTORS = Set[:hash, :object_id, :__id__].freeze
|
|
47
|
+
|
|
30
48
|
# Shared root for the offline-generated catalogues. Resolving it
|
|
31
49
|
# here keeps the repo-relative `../../../../` hop in one place
|
|
32
50
|
# instead of copying it into every per-topic loader.
|
|
@@ -59,6 +77,7 @@ module Rigor
|
|
|
59
77
|
|
|
60
78
|
def safe_for_folding?(class_name, selector, kind: :instance)
|
|
61
79
|
class_name_str = class_name.to_s
|
|
80
|
+
return false if NON_REPRODUCIBLE_SELECTORS.include?(selector.to_sym)
|
|
62
81
|
return false if blocked?(class_name_str, selector)
|
|
63
82
|
|
|
64
83
|
entry = method_entry(class_name_str, selector, kind: kind)
|
|
@@ -22,7 +22,15 @@ module Rigor
|
|
|
22
22
|
:replace, :initialize, :initialize_copy, :clear, :<<, :concat, :insert,
|
|
23
23
|
:prepend, :force_encoding, :encode, :scrub, :unicode_normalize, :"[]=",
|
|
24
24
|
:upto, :each_byte, :each_char, :each_codepoint,
|
|
25
|
-
:each_grapheme_cluster, :each_line, :bytesplice
|
|
25
|
+
:each_grapheme_cluster, :each_line, :bytesplice,
|
|
26
|
+
# `crypt` is not a mutator but is blocked from folding for the
|
|
27
|
+
# same "do not bake a non-pure result into a Constant" reason:
|
|
28
|
+
# `rb_str_crypt` delegates to the platform `crypt(3)`, whose
|
|
29
|
+
# output (algorithm and digest) varies by libc / OS, so
|
|
30
|
+
# `"x".crypt("ab")` is not deterministic across the platforms
|
|
31
|
+
# an analyzed project may target. The catalog classifies it
|
|
32
|
+
# `:leaf` from its C body; this entry overrides that.
|
|
33
|
+
:crypt
|
|
26
34
|
],
|
|
27
35
|
"Symbol" => Set[
|
|
28
36
|
# Symbol is immutable in Ruby; the classifier mis-flags
|